mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-26 14:37:44 +01:00
Merge pull request #1726 from tgstation/NullableAtLast
Taking further steps towards full nullable reference type support
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: Linting -->
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.507">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
<TgsClientVersion>15.0.0</TgsClientVersion>
|
||||
<TgsDmapiVersion>7.0.0</TgsDmapiVersion>
|
||||
<TgsInteropVersion>5.7.0</TgsInteropVersion>
|
||||
<TgsHostWatchdogVersion>1.4.0</TgsHostWatchdogVersion>
|
||||
<TgsHostWatchdogVersion>1.4.1</TgsHostWatchdogVersion>
|
||||
<TgsContainerScriptVersion>1.2.1</TgsContainerScriptVersion>
|
||||
<TgsMigratorVersion>2.0.0</TgsMigratorVersion>
|
||||
<TgsNugetNetFramework>netstandard2.0</TgsNugetNetFramework>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
public enum EngineType
|
||||
{
|
||||
/// <summary>
|
||||
/// Build your own net dream,
|
||||
/// Build your own net dream.
|
||||
/// </summary>
|
||||
Byond,
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Tgstation.Server.Api.Models
|
||||
IOError,
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ApiHeaders"/> failed to validate!
|
||||
/// The <see cref="ApiHeaders"/> failed to validate.
|
||||
/// </summary>
|
||||
[Description("A header validation error occurred!")]
|
||||
BadHeaders,
|
||||
@@ -324,7 +324,7 @@ namespace Tgstation.Server.Api.Models
|
||||
EngineNoVersionsInstalled,
|
||||
|
||||
/// <summary>
|
||||
/// The DMAPI never validated itself
|
||||
/// The DMAPI never validated itself.
|
||||
/// </summary>
|
||||
[Description("DMAPI validation failed! See FAQ at https://github.com/tgstation/tgstation-server/discussions/1695")]
|
||||
DeploymentNeverValidated,
|
||||
@@ -360,7 +360,7 @@ namespace Tgstation.Server.Api.Models
|
||||
DeploymentExitCode,
|
||||
|
||||
/// <summary>
|
||||
/// Deployment already in progress
|
||||
/// Deployment already in progress.
|
||||
/// </summary>
|
||||
[Description("There is already a deployment operation in progress!")]
|
||||
DeploymentInProgress,
|
||||
@@ -600,7 +600,7 @@ namespace Tgstation.Server.Api.Models
|
||||
FileUploadExpired,
|
||||
|
||||
/// <summary>
|
||||
/// Tried to update a user to have both a <see cref="Internal.UserApiBase.Group"/> and <see cref="Internal.UserApiBase.PermissionSet"/>
|
||||
/// Tried to update a user to have both a <see cref="Internal.UserApiBase.Group"/> and <see cref="Internal.UserApiBase.PermissionSet"/>.
|
||||
/// </summary>
|
||||
[Description("A user may not have both a permissionSet and group!")]
|
||||
UserGroupAndPermissionSet,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
public enum FieldPresence
|
||||
{
|
||||
/// <summary>
|
||||
/// The field is optional
|
||||
/// The field is optional.
|
||||
/// </summary>
|
||||
Optional,
|
||||
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
public enum IrcPasswordType
|
||||
{
|
||||
/// <summary>
|
||||
/// Use server authentication
|
||||
/// Use server authentication.
|
||||
/// </summary>
|
||||
Server,
|
||||
|
||||
/// <summary>
|
||||
/// Use PLAIN sasl authentication
|
||||
/// Use PLAIN sasl authentication.
|
||||
/// </summary>
|
||||
Sasl,
|
||||
|
||||
/// <summary>
|
||||
/// Use NickServ authentication
|
||||
/// Use NickServ authentication.
|
||||
/// </summary>
|
||||
NickServ,
|
||||
}
|
||||
|
||||
@@ -10,27 +10,27 @@ namespace Tgstation.Server.Api.Models
|
||||
public enum OAuthProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// https://github.com
|
||||
/// https://github.com.
|
||||
/// </summary>
|
||||
GitHub,
|
||||
|
||||
/// <summary>
|
||||
/// https://discord.com
|
||||
/// https://discord.com.
|
||||
/// </summary>
|
||||
Discord,
|
||||
|
||||
/// <summary>
|
||||
/// https://tgstation13.org
|
||||
/// https://tgstation13.org.
|
||||
/// </summary>
|
||||
TGForums,
|
||||
|
||||
/// <summary>
|
||||
/// https://www.keycloak.org
|
||||
/// https://www.keycloak.org.
|
||||
/// </summary>
|
||||
Keycloak,
|
||||
|
||||
/// <summary>
|
||||
/// https://invisioncommunity.com/
|
||||
/// https://invisioncommunity.com.
|
||||
/// </summary>
|
||||
InvisionCommunity,
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// Remote provider is GitHub.com
|
||||
/// Remote provider is GitHub.com.
|
||||
/// </summary>
|
||||
GitHub,
|
||||
|
||||
/// <summary>
|
||||
/// Remote provider is GitLab.com
|
||||
/// Remote provider is GitLab.com.
|
||||
/// </summary>
|
||||
GitLab,
|
||||
}
|
||||
|
||||
@@ -16,6 +16,6 @@ namespace Tgstation.Server.Api.Models.Response
|
||||
/// Parses the <see cref="Bearer"/> as a <see cref="JsonWebToken"/>.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="JsonWebToken"/> based on <see cref="Bearer"/>.</returns>
|
||||
public JsonWebToken ParseJwt() => new (Bearer);
|
||||
public JsonWebToken ParseJwt() => new(Bearer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
public enum AdministrationRights : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights
|
||||
/// User has no rights.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
WriteChannels = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.ChatBotSettings.ConnectionString"/>
|
||||
/// User can change <see cref="Models.Internal.ChatBotSettings.ConnectionString"/>.
|
||||
/// </summary>
|
||||
WriteConnectionString = 1 << 3,
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
ReadConnectionString = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// User can read all chat bot properties except <see cref="Models.Internal.ChatBotSettings.ConnectionString"/>
|
||||
/// User can read all chat bot properties except <see cref="Models.Internal.ChatBotSettings.ConnectionString"/>.
|
||||
/// </summary>
|
||||
Read = 1 << 5,
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
SetStartupTimeout = 1 << 11,
|
||||
|
||||
/// <summary>
|
||||
/// User can change <see cref="Models.Internal.DreamDaemonLaunchParameters.HealthCheckSeconds"/>
|
||||
/// User can change <see cref="Models.Internal.DreamDaemonLaunchParameters.HealthCheckSeconds"/>.
|
||||
/// </summary>
|
||||
SetHealthCheckInterval = 1 << 12,
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
public enum InstancePermissionSetRights : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights/
|
||||
/// User has no rights.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
|
||||
@@ -6,47 +6,47 @@
|
||||
public enum RightsType : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="AdministrationRights"/>
|
||||
/// <see cref="AdministrationRights"/>.
|
||||
/// </summary>
|
||||
Administration,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="InstanceManagerRights"/>
|
||||
/// <see cref="InstanceManagerRights"/>.
|
||||
/// </summary>
|
||||
InstanceManager,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="RepositoryRights"/>
|
||||
/// <see cref="RepositoryRights"/>.
|
||||
/// </summary>
|
||||
Repository,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="EngineRights"/>
|
||||
/// <see cref="EngineRights"/>.
|
||||
/// </summary>
|
||||
Engine,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DreamMakerRights"/>
|
||||
/// <see cref="DreamMakerRights"/>.
|
||||
/// </summary>
|
||||
DreamMaker,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DreamDaemonRights"/>
|
||||
/// <see cref="DreamDaemonRights"/>.
|
||||
/// </summary>
|
||||
DreamDaemon,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ChatBotRights"/>
|
||||
/// <see cref="ChatBotRights"/>.
|
||||
/// </summary>
|
||||
ChatBots,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ConfigurationRights"/>
|
||||
/// <see cref="ConfigurationRights"/>.
|
||||
/// </summary>
|
||||
Configuration,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="InstancePermissionSetRights"/>
|
||||
/// <see cref="InstancePermissionSetRights"/>.
|
||||
/// </summary>
|
||||
InstancePermissionSet,
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Client
|
||||
/// PATCH <see cref="HttpMethod"/>.
|
||||
/// </summary>
|
||||
/// <remarks>HOW IS THIS NOT INCLUDED IN THE FRAMEWORK??!?!?</remarks>
|
||||
static readonly HttpMethod HttpPatch = new ("PATCH");
|
||||
static readonly HttpMethod HttpPatch = new("PATCH");
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri Url { get; }
|
||||
@@ -59,7 +59,7 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// The <see cref="JsonSerializerSettings"/> to use.
|
||||
/// </summary>
|
||||
static readonly JsonSerializerSettings SerializerSettings = new ()
|
||||
static readonly JsonSerializerSettings SerializerSettings = new()
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
Converters = new[]
|
||||
@@ -126,9 +126,7 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0010 // Add missing cases
|
||||
#pragma warning disable IDE0066 // Convert switch statement to expression
|
||||
switch (response.StatusCode)
|
||||
#pragma warning restore IDE0066 // Convert switch statement to expression
|
||||
#pragma warning restore IDE0010 // Add missing cases
|
||||
{
|
||||
case HttpStatusCode.Unauthorized:
|
||||
@@ -310,9 +308,7 @@ namespace Tgstation.Server.Client
|
||||
|
||||
using (memoryStream)
|
||||
{
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope
|
||||
var streamContent = new StreamContent(uploadStream ?? memoryStream);
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
try
|
||||
{
|
||||
await RunRequest<object>(
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace Tgstation.Server.Common.Extensions
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exceptions ??= new (tasks.Count - i);
|
||||
exceptions ??= new(tasks.Count - i);
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(TgsNugetNetFramework)</TargetFrameworks>
|
||||
<Version>$(TgsCommonLibraryVersion)</Version>
|
||||
<Nullable>enable</Nullable>
|
||||
<Description>Common functions for tgstation-server.</Description>
|
||||
<PackageTags>web tgstation-server tgstation ss13 byond client http</PackageTags>
|
||||
<PackageReleaseNotes>$(TGS_NUGET_RELEASE_NOTES_COMMON)</PackageReleaseNotes>
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Tgstation.Server.Host.Console
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask CheckSignals(Func<string, (int, Task)> startChild, CancellationToken cancellationToken)
|
||||
public async ValueTask CheckSignals(Func<string?, (int, Task)> startChild, CancellationToken cancellationToken)
|
||||
{
|
||||
var (childPid, _) = startChild?.Invoke(null) ?? throw new ArgumentNullException(nameof(startChild));
|
||||
var signalTcs = new TaskCompletionSource<Signum>();
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Tgstation.Server.Host.Console
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
internal static async Task<int> Main(string[] args)
|
||||
{
|
||||
System.Console.Title = $"{Constants.CanonicalPackageName} Host Watchdog v{Assembly.GetExecutingAssembly().GetName().Version.Semver()}";
|
||||
System.Console.Title = $"{Constants.CanonicalPackageName} Host Watchdog v{Assembly.GetExecutingAssembly().GetName().Version?.Semver()}";
|
||||
|
||||
var arguments = new List<string>(args);
|
||||
var trace = arguments.Remove("--trace-host-watchdog");
|
||||
@@ -61,7 +61,7 @@ namespace Tgstation.Server.Host.Console
|
||||
}
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
void AppDomainHandler(object a, EventArgs b) => cts.Cancel();
|
||||
void AppDomainHandler(object? a, EventArgs b) => cts.Cancel();
|
||||
AppDomain.CurrentDomain.ProcessExit += AppDomainHandler;
|
||||
try
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>$(TgsFrameworkVersion)</TargetFramework>
|
||||
<Version>$(TgsCoreVersion)</Version>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseAppHost>false</UseAppHost>
|
||||
<ApplicationManifest>../../build/uac_elevation_manifest.xml</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Tgstation.Server.Host.Service
|
||||
/// The --passthroughargs or -p option.
|
||||
/// </summary>
|
||||
[Option(ShortName = "p", Description = "Arguments passed to main host process")]
|
||||
public string PassthroughArgs { get; set; }
|
||||
public string? PassthroughArgs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Entrypoint for the application.
|
||||
@@ -157,7 +157,7 @@ namespace Tgstation.Server.Host.Service
|
||||
/// Runs sc.exe to either uninstall a given <paramref name="serviceToUninstall"/> or install the running <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
/// <param name="serviceToUninstall">The name of a service to uninstall.</param>
|
||||
void InvokeSC(string serviceToUninstall)
|
||||
void InvokeSC(string? serviceToUninstall)
|
||||
{
|
||||
using var installer = new ServiceInstaller();
|
||||
if (serviceToUninstall != null)
|
||||
@@ -172,6 +172,9 @@ namespace Tgstation.Server.Host.Service
|
||||
Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
var assemblyDirectory = Path.GetDirectoryName(fullPathToAssembly);
|
||||
if (assemblyDirectory == null)
|
||||
throw new InvalidOperationException($"Failed to resolve directory name of {assemblyDirectory}");
|
||||
|
||||
var assemblyNameWithoutExtension = Path.GetFileNameWithoutExtension(fullPathToAssembly);
|
||||
var exePath = Path.Combine(assemblyDirectory, $"{assemblyNameWithoutExtension}.exe");
|
||||
|
||||
@@ -260,10 +263,8 @@ namespace Tgstation.Server.Host.Service
|
||||
var stop = !Detach;
|
||||
if (!stop)
|
||||
{
|
||||
serviceController.ExecuteCommand(
|
||||
PipeCommands.GetServiceCommandId(
|
||||
PipeCommands.CommandDetachingShutdown)
|
||||
.Value);
|
||||
var serviceControllerCommand = PipeCommands.GetServiceCommandId(PipeCommands.CommandDetachingShutdown);
|
||||
serviceController.ExecuteCommand(serviceControllerCommand!.Value);
|
||||
serviceController.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30));
|
||||
if (serviceController.Status != ServiceControllerStatus.Stopped)
|
||||
stop = true;
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Linq;
|
||||
using System.Runtime.Versioning;
|
||||
using System.ServiceProcess;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.EventLog;
|
||||
|
||||
using Tgstation.Server.Common;
|
||||
using Tgstation.Server.Host.Common;
|
||||
using Tgstation.Server.Host.Watchdog;
|
||||
|
||||
namespace Tgstation.Server.Host.Service
|
||||
@@ -23,7 +17,7 @@ namespace Tgstation.Server.Host.Service
|
||||
/// Represents a <see cref="IWatchdog"/> as a <see cref="ServiceBase"/>.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
sealed class ServerService : ServiceBase, ISignalChecker
|
||||
sealed class ServerService : ServiceBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The canonical windows service name.
|
||||
@@ -35,45 +29,22 @@ namespace Tgstation.Server.Host.Service
|
||||
/// </summary>
|
||||
readonly IWatchdogFactory watchdogFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Lazy{T}"/> <see cref="ILoggerFactory"/> used by the <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
readonly Lazy<ILoggerFactory> loggerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Array"/> of command line arguments the service was invoked with.
|
||||
/// </summary>
|
||||
readonly string[] commandLineArguments;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum <see cref="LogLevel"/> for the <see cref="EventLog"/>.
|
||||
/// The active <see cref="ServiceLifetime"/>.
|
||||
/// </summary>
|
||||
readonly LogLevel minimumLogLevel;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILoggerFactory"/> used by the <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
ILoggerFactory loggerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
ILogger<ServerService> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Task"/> that represents the running <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
Task watchdogTask;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="cancellationTokenSource"/> for the <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
CancellationTokenSource cancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AnonymousPipeServerStream"/> for sending <see cref="PipeCommands"/> to the server process.
|
||||
/// </summary>
|
||||
AnonymousPipeServerStream commandPipeServer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AnonymousPipeServerStream"/> for receiving the <see cref="PipeCommands.CommandStartupComplete"/>.
|
||||
/// </summary>
|
||||
AnonymousPipeServerStream readyPipeServer;
|
||||
#pragma warning disable CA2213 // Disposable fields should be disposed
|
||||
volatile ServiceLifetime? serviceLifetime;
|
||||
#pragma warning restore CA2213 // Disposable fields should be disposed
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerService"/> class.
|
||||
@@ -85,21 +56,15 @@ namespace Tgstation.Server.Host.Service
|
||||
{
|
||||
this.watchdogFactory = watchdogFactory ?? throw new ArgumentNullException(nameof(watchdogFactory));
|
||||
this.commandLineArguments = commandLineArguments ?? throw new ArgumentNullException(nameof(commandLineArguments));
|
||||
this.minimumLogLevel = minimumLogLevel;
|
||||
ServiceName = Name;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask CheckSignals(Func<string, (int, Task)> startChildAndGetPid, CancellationToken cancellationToken)
|
||||
{
|
||||
await using (commandPipeServer = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable))
|
||||
await using (readyPipeServer = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable))
|
||||
ServiceName = Name;
|
||||
loggerFactory = new Lazy<ILoggerFactory>(() => LoggerFactory.Create(builder => builder.AddEventLog(new EventLogSettings
|
||||
{
|
||||
var (_, lifetimeTask) = startChildAndGetPid($"--Internal:CommandPipe={commandPipeServer.GetClientHandleAsString()} --Internal:ReadyPipe={readyPipeServer.GetClientHandleAsString()}");
|
||||
commandPipeServer.DisposeLocalCopyOfClientHandle();
|
||||
readyPipeServer.DisposeLocalCopyOfClientHandle();
|
||||
await lifetimeTask;
|
||||
}
|
||||
LogName = EventLog.Log,
|
||||
MachineName = EventLog.MachineName,
|
||||
SourceName = EventLog.Source,
|
||||
Filter = (message, logLevel) => logLevel >= minimumLogLevel,
|
||||
})));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -112,53 +77,21 @@ namespace Tgstation.Server.Host.Service
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
loggerFactory?.Dispose();
|
||||
cancellationTokenSource?.Dispose();
|
||||
commandPipeServer?.Dispose();
|
||||
readyPipeServer?.Dispose();
|
||||
OnStop();
|
||||
|
||||
if (loggerFactory.IsValueCreated)
|
||||
loggerFactory.Value.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnCustomCommand(int command)
|
||||
{
|
||||
var commandsToCheck = PipeCommands.AllCommands;
|
||||
foreach (var stringCommand in commandsToCheck)
|
||||
{
|
||||
var commandId = PipeCommands.GetServiceCommandId(stringCommand);
|
||||
if (command == commandId)
|
||||
{
|
||||
SendCommandToHostThroughPipe(stringCommand);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogWarning("Received unknown service command: {command}", command);
|
||||
}
|
||||
protected override void OnCustomCommand(int command) => serviceLifetime!.HandleCustomCommand(command);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnStart(string[] args)
|
||||
{
|
||||
if (loggerFactory == null)
|
||||
{
|
||||
loggerFactory = LoggerFactory.Create(builder => builder.AddEventLog(new EventLogSettings
|
||||
{
|
||||
LogName = EventLog.Log,
|
||||
MachineName = EventLog.MachineName,
|
||||
SourceName = EventLog.Source,
|
||||
Filter = (message, logLevel) => logLevel >= minimumLogLevel,
|
||||
}));
|
||||
|
||||
logger = loggerFactory.CreateLogger<ServerService>();
|
||||
}
|
||||
|
||||
var watchdog = watchdogFactory.CreateWatchdog(this, loggerFactory);
|
||||
|
||||
cancellationTokenSource?.Dispose();
|
||||
cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
var newArgs = new List<string>(commandLineArguments.Length + args.Length + 1)
|
||||
{
|
||||
"--General:SetupWizardMode=Never",
|
||||
@@ -167,94 +100,18 @@ namespace Tgstation.Server.Host.Service
|
||||
newArgs.AddRange(commandLineArguments);
|
||||
newArgs.AddRange(args);
|
||||
|
||||
watchdogTask = RunWatchdog(watchdog, newArgs.ToArray(), cancellationTokenSource.Token);
|
||||
|
||||
if (!watchdogTask.IsCompleted && watchdog.InitialHostVersion >= new Version(5, 14, 0))
|
||||
{
|
||||
logger.LogInformation("Waiting for host to finish starting...");
|
||||
using var streamReader = new StreamReader(
|
||||
readyPipeServer,
|
||||
Encoding.UTF8,
|
||||
leaveOpen: true);
|
||||
|
||||
var line = streamReader.ReadLine(); // Intentionally blocking service startup
|
||||
logger.LogDebug("Pipe read: {line}", line);
|
||||
}
|
||||
|
||||
// Maybe we'll use this pipe more in the future, but for now leaving it open is just a resource waste
|
||||
readyPipeServer.Dispose();
|
||||
serviceLifetime = new ServiceLifetime(
|
||||
Stop,
|
||||
signalChecker => watchdogFactory.CreateWatchdog(signalChecker, loggerFactory.Value),
|
||||
loggerFactory.Value.CreateLogger<ServiceLifetime>(),
|
||||
newArgs.ToArray());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnStop()
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
watchdogTask.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the <paramref name="watchdog"/>, stopping the service if it exits.
|
||||
/// </summary>
|
||||
/// <param name="watchdog">The <see cref="IWatchdog"/> to run.</param>
|
||||
/// <param name="args">The arguments for the <paramref name="watchdog"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
async Task RunWatchdog(IWatchdog watchdog, string[] args, CancellationToken cancellationToken)
|
||||
{
|
||||
await watchdog.RunAsync(false, args, cancellationToken);
|
||||
|
||||
async void StopServiceAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Run(Stop, cancellationToken); // DCT intentional
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
logger.LogTrace(ex, "Stopping service cancelled!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error stopping service!");
|
||||
}
|
||||
}
|
||||
|
||||
StopServiceAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a command to the main server process.
|
||||
/// </summary>
|
||||
/// <param name="command">One of the <see cref="PipeCommands"/>.</param>
|
||||
void SendCommandToHostThroughPipe(string command)
|
||||
{
|
||||
var localPipeServer = commandPipeServer;
|
||||
if (localPipeServer == null)
|
||||
{
|
||||
logger.LogWarning("Unable to send command \"{command}\" to main server process. Is the service running?", command);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogDebug("Send command: {command}", command);
|
||||
try
|
||||
{
|
||||
var encoding = Encoding.UTF8;
|
||||
using var streamWriter = new StreamWriter(
|
||||
localPipeServer,
|
||||
encoding,
|
||||
PipeCommands
|
||||
.AllCommands
|
||||
.Select(
|
||||
command => encoding.GetByteCount(
|
||||
command + Environment.NewLine))
|
||||
.Max(),
|
||||
true);
|
||||
streamWriter.WriteLine(command);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error attempting to send command \"{command}\"", command);
|
||||
}
|
||||
var oldLifetime = Interlocked.Exchange(ref serviceLifetime, null);
|
||||
oldLifetime?.DisposeAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Tgstation.Server.Host.Common;
|
||||
using Tgstation.Server.Host.Watchdog;
|
||||
|
||||
namespace Tgstation.Server.Host.Service
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the lifetime of the service.
|
||||
/// </summary>
|
||||
sealed class ServiceLifetime : ISignalChecker, IAsyncDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
readonly ILogger<ServiceLifetime> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Task"/> that represents the running <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
readonly Task watchdogTask;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="cancellationTokenSource"/> for the <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
readonly CancellationTokenSource cancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AnonymousPipeServerStream"/> for sending <see cref="PipeCommands"/> to the server process.
|
||||
/// </summary>
|
||||
AnonymousPipeServerStream? commandPipeServer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AnonymousPipeServerStream"/> for receiving the <see cref="PipeCommands.CommandStartupComplete"/>.
|
||||
/// </summary>
|
||||
AnonymousPipeServerStream? readyPipeServer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServiceLifetime"/> class.
|
||||
/// </summary>
|
||||
/// <param name="stopService">An <see cref="Action"/> to manually stop the service.</param>
|
||||
/// <param name="watchdogFactory">A <see cref="Func{T, TResult}"/> taking a <see cref="ISignalChecker"/> and returning the <see cref="IWatchdog"/> to run.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
/// <param name="args">The arguments for the <see cref="IWatchdog"/>.</param>
|
||||
public ServiceLifetime(Action stopService, Func<ISignalChecker, IWatchdog> watchdogFactory, ILogger<ServiceLifetime> logger, string[] args)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stopService);
|
||||
ArgumentNullException.ThrowIfNull(watchdogFactory);
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
ArgumentNullException.ThrowIfNull(args);
|
||||
|
||||
cancellationTokenSource = new CancellationTokenSource();
|
||||
watchdogTask = RunWatchdog(
|
||||
stopService,
|
||||
watchdogFactory(this),
|
||||
args,
|
||||
cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
cancellationTokenSource.Cancel();
|
||||
await watchdogTask;
|
||||
cancellationTokenSource.Dispose();
|
||||
|
||||
if (commandPipeServer != null)
|
||||
await commandPipeServer.DisposeAsync();
|
||||
|
||||
if (readyPipeServer != null)
|
||||
await readyPipeServer.DisposeAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask CheckSignals(Func<string, (int, Task)> startChildAndGetPid, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await using (commandPipeServer = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable))
|
||||
await using (readyPipeServer = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable))
|
||||
{
|
||||
var (_, lifetimeTask) = startChildAndGetPid($"--Internal:CommandPipe={commandPipeServer.GetClientHandleAsString()} --Internal:ReadyPipe={readyPipeServer.GetClientHandleAsString()}");
|
||||
commandPipeServer.DisposeLocalCopyOfClientHandle();
|
||||
readyPipeServer.DisposeLocalCopyOfClientHandle();
|
||||
await lifetimeTask;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
readyPipeServer = null;
|
||||
commandPipeServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a custom service <paramref name="command"/>.
|
||||
/// </summary>
|
||||
/// <param name="command">The <see cref="int"/> command sent to the service.</param>
|
||||
public void HandleCustomCommand(int command)
|
||||
{
|
||||
var commandsToCheck = PipeCommands.AllCommands;
|
||||
foreach (var stringCommand in commandsToCheck)
|
||||
{
|
||||
var commandId = PipeCommands.GetServiceCommandId(stringCommand);
|
||||
if (command == commandId)
|
||||
{
|
||||
SendCommandToHostThroughPipe(stringCommand);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogWarning("Received unknown service command: {command}", command);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the <paramref name="watchdog"/>, stopping the service if it exits.
|
||||
/// </summary>
|
||||
/// <param name="stopService">An <see cref="Action"/> to manually stop the service.</param>
|
||||
/// <param name="watchdog">The <see cref="IWatchdog"/> to run.</param>
|
||||
/// <param name="args">The arguments for the <paramref name="watchdog"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
async Task RunWatchdog(Action stopService, IWatchdog watchdog, string[] args, CancellationToken cancellationToken)
|
||||
{
|
||||
var localWatchdogTask = watchdog.RunAsync(false, args, cancellationToken);
|
||||
|
||||
if (!localWatchdogTask.IsCompleted && (await watchdog.InitialHostVersion) >= new Version(5, 14, 0))
|
||||
if (readyPipeServer != null)
|
||||
{
|
||||
logger.LogInformation("Waiting for host to finish starting...");
|
||||
using var streamReader = new StreamReader(
|
||||
readyPipeServer,
|
||||
Encoding.UTF8,
|
||||
leaveOpen: true);
|
||||
|
||||
var line = streamReader.ReadLine(); // Intentionally blocking service startup
|
||||
logger.LogDebug("Pipe read: {line}", line);
|
||||
|
||||
// Maybe we'll use this pipe more in the future, but for now leaving it open is just a resource waste
|
||||
readyPipeServer.Dispose();
|
||||
}
|
||||
else
|
||||
logger.LogError("Watchdog started and ready pipe was not initialized!");
|
||||
|
||||
await localWatchdogTask;
|
||||
|
||||
async void StopServiceAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// This can call OnStop which waits on this task to complete, must be threaded off or it will deadlock
|
||||
await Task.Run(stopService, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
logger.LogDebug(ex, "Stopping service cancelled!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error stopping service!");
|
||||
}
|
||||
}
|
||||
|
||||
StopServiceAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a command to the main server process.
|
||||
/// </summary>
|
||||
/// <param name="command">One of the <see cref="PipeCommands"/>.</param>
|
||||
void SendCommandToHostThroughPipe(string command)
|
||||
{
|
||||
var localPipeServer = commandPipeServer;
|
||||
if (localPipeServer == null)
|
||||
{
|
||||
logger.LogWarning("Unable to send command \"{command}\" to main server process. Is the service running?", command);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogDebug("Send command: {command}", command);
|
||||
try
|
||||
{
|
||||
var encoding = Encoding.UTF8;
|
||||
using var streamWriter = new StreamWriter(
|
||||
localPipeServer,
|
||||
encoding,
|
||||
PipeCommands
|
||||
.AllCommands
|
||||
.Select(
|
||||
command => encoding.GetByteCount(
|
||||
command + Environment.NewLine))
|
||||
.Max(),
|
||||
true);
|
||||
streamWriter.WriteLine(command);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error attempting to send command \"{command}\"", command);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RuntimeIdentifiers>win-x86;win-x64</RuntimeIdentifiers>
|
||||
<Nullable>enable</Nullable>
|
||||
<!-- DO NOT ADD THE -windows SUFFIX, It makes the service require the desktop runtime instead of the ASP NET Core Hosting Bundle -->
|
||||
<TargetFramework>$(TgsFrameworkVersion)</TargetFramework>
|
||||
<Version>$(TgsCoreVersion)</Version>
|
||||
|
||||
@@ -12,9 +12,9 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
/// <summary>
|
||||
/// Relays signals received to the host process.
|
||||
/// </summary>
|
||||
/// <param name="startChild">An <see cref="Func{TResult}"/> to start the main process. It accepts an optional additional command line argument as a paramter and returns it's <see cref="System.Diagnostics.Process.Id"/> and lifetime <see cref="Task"/>.</param>
|
||||
/// <param name="startChildAndGetPid">An <see cref="Func{TResult}"/> to start the main process. It accepts an optional additional command line argument as a paramter and returns it's <see cref="System.Diagnostics.Process.Id"/> and lifetime <see cref="Task"/>. Must be called.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
|
||||
ValueTask CheckSignals(Func<string, (int, Task)> startChild, CancellationToken cancellationToken);
|
||||
ValueTask CheckSignals(Func<string?, (int Pid, Task ChildLifetime)> startChildAndGetPid, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
public interface IWatchdog
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current version of the host process. Set once <see cref="RunAsync(bool, string[], CancellationToken)"/> begins and doesn't immediately return <see langword="false"/>.
|
||||
/// Gets a <see cref="Task{TResult}"/> resulting in the current version of the host process. Guaranteed to complete once <see cref="RunAsync(bool, string[], CancellationToken)"/> begins and doesn't immediately return <see langword="false"/>.
|
||||
/// </summary>
|
||||
Version InitialHostVersion { get; }
|
||||
Task<Version> InitialHostVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Run the <see cref="IWatchdog"/>.
|
||||
|
||||
@@ -10,10 +10,10 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
public sealed class NoopSignalChecker : ISignalChecker
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ValueTask CheckSignals(Func<string, (int, Task)> startChild, CancellationToken cancellationToken)
|
||||
public ValueTask CheckSignals(Func<string?, (int, Task)> startChildAndGetPid, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(startChild);
|
||||
startChild(null);
|
||||
ArgumentNullException.ThrowIfNull(startChildAndGetPid);
|
||||
startChildAndGetPid(null);
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>$(TgsFrameworkVersion)</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<AddSyntheticProjectReferencesForSolutionDependencies>false</AddSyntheticProjectReferencesForSolutionDependencies>
|
||||
<Version>$(TgsHostWatchdogVersion)</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
sealed class Watchdog : IWatchdog
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Version InitialHostVersion { get; private set; }
|
||||
public Task<Version> InitialHostVersion => initialHostVersionTcs.Task;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISignalChecker"/> for the <see cref="Watchdog"/>.
|
||||
@@ -32,6 +32,11 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
/// </summary>
|
||||
readonly ILogger<Watchdog> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Backing <see cref="TaskCompletionSource{TResult}"/> for <see cref="InitialHostVersion"/>.
|
||||
/// </summary>
|
||||
readonly TaskCompletionSource<Version> initialHostVersionTcs;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Watchdog"/> class.
|
||||
/// </summary>
|
||||
@@ -41,6 +46,8 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
{
|
||||
this.signalChecker = signalChecker ?? throw new ArgumentNullException(nameof(signalChecker));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
initialHostVersionTcs = new TaskCompletionSource<Version>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -54,7 +61,7 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
currentProcessId = currentProc.Id;
|
||||
|
||||
logger.LogDebug("PID: {pid}", currentProcessId);
|
||||
string updateDirectory = null;
|
||||
string? updateDirectory = null;
|
||||
try
|
||||
{
|
||||
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
@@ -76,6 +83,11 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
|
||||
var executingAssembly = Assembly.GetExecutingAssembly();
|
||||
var rootLocation = Path.GetDirectoryName(executingAssembly.Location);
|
||||
if (rootLocation == null)
|
||||
{
|
||||
logger.LogCritical("Failed to get the directory name of the executing assembly: {location}", executingAssembly.Location);
|
||||
return false;
|
||||
}
|
||||
|
||||
var assemblyStoragePath = Path.Combine(rootLocation, "lib"); // always always next to watchdog
|
||||
|
||||
@@ -89,7 +101,10 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
Directory.Delete(assemblyStoragePath, true);
|
||||
Directory.CreateDirectory(defaultAssemblyPath);
|
||||
|
||||
var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/net8.0";
|
||||
var sourcePath = Path.GetFullPath(
|
||||
Path.Combine(
|
||||
rootLocation,
|
||||
"../../../../Tgstation.Server.Host/bin/Debug/net8.0"));
|
||||
foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
|
||||
Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath, StringComparison.Ordinal));
|
||||
|
||||
@@ -119,9 +134,18 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
return false;
|
||||
}
|
||||
|
||||
InitialHostVersion = Version.Parse(FileVersionInfo.GetVersionInfo(assemblyPath).FileVersion);
|
||||
var fileVersion = FileVersionInfo.GetVersionInfo(assemblyPath).FileVersion;
|
||||
if (fileVersion == null)
|
||||
{
|
||||
logger.LogCritical("Failed to parse version info from {assemblyPath}!", assemblyPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
var watchdogVersion = executingAssembly.GetName().Version.Semver().ToString();
|
||||
initialHostVersionTcs.SetResult(
|
||||
Version.Parse(
|
||||
fileVersion));
|
||||
|
||||
var watchdogVersion = executingAssembly.GetName().Version?.Semver().ToString();
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
using (logger.BeginScope("Host invocation"))
|
||||
@@ -158,8 +182,8 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
var killedHostProcess = false;
|
||||
try
|
||||
{
|
||||
Task processTask = null;
|
||||
(int, Task) StartProcess(string additionalArg)
|
||||
Task? processTask = null;
|
||||
(int, Task) StartProcess(string? additionalArg)
|
||||
{
|
||||
if (additionalArg != null)
|
||||
process.StartInfo.Arguments += $" {additionalArg}";
|
||||
@@ -199,7 +223,7 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
var checkerTask = signalChecker.CheckSignals(StartProcess, cts.Token);
|
||||
try
|
||||
{
|
||||
await processTask;
|
||||
await processTask!;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -338,10 +362,11 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
logger.LogDebug(ex, "Exiting due to cancellation...");
|
||||
if (!Directory.Exists(updateDirectory))
|
||||
File.Delete(updateDirectory);
|
||||
else
|
||||
Directory.Delete(updateDirectory, true);
|
||||
if (updateDirectory != null)
|
||||
if (!Directory.Exists(updateDirectory))
|
||||
File.Delete(updateDirectory);
|
||||
else
|
||||
Directory.Delete(updateDirectory, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -337,7 +337,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
var completionString = errorMessage == null ? "Pending" : "Failed";
|
||||
|
||||
Embed CreateUpdatedEmbed(string message, Color color) => new ()
|
||||
Embed CreateUpdatedEmbed(string message, Color color) => new()
|
||||
{
|
||||
Author = embed.Author,
|
||||
Colour = color,
|
||||
|
||||
@@ -351,7 +351,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
dbChannel,
|
||||
new List<ChannelRepresentation>
|
||||
{
|
||||
new ()
|
||||
new()
|
||||
{
|
||||
RealId = id.Value,
|
||||
IsAdminChannel = dbChannel.IsAdminChannel == true,
|
||||
|
||||
@@ -41,12 +41,12 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// <summary>
|
||||
/// The first <see cref="Version"/> of BYOND that supports the '-map-threads' parameter on DreamDaemon.
|
||||
/// </summary>
|
||||
static readonly Version MapThreadsVersion = new (515, 1609);
|
||||
static readonly Version MapThreadsVersion = new(515, 1609);
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SemaphoreSlim"/> for writing to files in the user's BYOND directory.
|
||||
/// </summary>
|
||||
static readonly SemaphoreSlim UserFilesSemaphore = new (1);
|
||||
static readonly SemaphoreSlim UserFilesSemaphore = new(1);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override EngineType TargetEngineType => EngineType.Byond;
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// <summary>
|
||||
/// The first version of BYOND to ship with dd.exe on the Windows build.
|
||||
/// </summary>
|
||||
public static Version DDExeVersion => new (515, 1598);
|
||||
public static Version DDExeVersion => new(515, 1598);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string DreamMakerName => "dm.exe";
|
||||
|
||||
@@ -6,92 +6,92 @@
|
||||
public enum EventType
|
||||
{
|
||||
/// <summary>
|
||||
/// Parameters: Reference name, commit sha
|
||||
/// Parameters: Reference name, commit sha.
|
||||
/// </summary>
|
||||
[EventScript("RepoResetOrigin")]
|
||||
RepoResetOrigin,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Checkout target
|
||||
/// Parameters: Checkout target.
|
||||
/// </summary>
|
||||
[EventScript("RepoCheckout")]
|
||||
RepoCheckout,
|
||||
|
||||
/// <summary>
|
||||
/// No parameters
|
||||
/// No parameters.
|
||||
/// </summary>
|
||||
[EventScript("RepoFetch")]
|
||||
RepoFetch,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Test merge number, test merge target sha, merger message
|
||||
/// Parameters: Test merge number, test merge target sha, merger message.
|
||||
/// </summary>
|
||||
[EventScript("RepoMergePullRequest")]
|
||||
RepoAddTestMerge,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Absolute path to repository root
|
||||
/// Parameters: Absolute path to repository root.
|
||||
/// </summary>
|
||||
/// <remarks>Changes made to the repository during this event will be pushed to the tracked branch if no test merges are present.</remarks>
|
||||
[EventScript("PreSynchronize")]
|
||||
RepoPreSynchronize,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Version being installed
|
||||
/// Parameters: Version being installed.
|
||||
/// </summary>
|
||||
[EventScript("ByondInstallStart", "EngineInstallStart")]
|
||||
EngineInstallStart,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Error string
|
||||
/// Parameters: Error string.
|
||||
/// </summary>
|
||||
[EventScript("ByondInstallFail", "EngineInstallFail")]
|
||||
EngineInstallFail,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Old active version, new active version
|
||||
/// Parameters: Old active version, new active version.
|
||||
/// </summary>
|
||||
[EventScript("ByondActiveVersionChange", "EngineActiveVersionChange")]
|
||||
EngineActiveVersionChange,
|
||||
|
||||
/// <summary>
|
||||
/// After the repo is copied, before CodeModifications are applied. Parameters: Game directory path, origin commit sha, engine version string
|
||||
/// After the repo is copied, before CodeModifications are applied. Parameters: Game directory path, origin commit sha, engine version string.
|
||||
/// </summary>
|
||||
[EventScript("PreCompile")]
|
||||
CompileStart,
|
||||
|
||||
/// <summary>
|
||||
/// No parameters
|
||||
/// No parameters.
|
||||
/// </summary>
|
||||
[EventScript("CompileCancelled")]
|
||||
CompileCancelled,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Game directory path, "1" if compile succeeded and api validation failed, "0" otherwise, engine version string
|
||||
/// Parameters: Game directory path, "1" if compile succeeded and api validation failed, "0" otherwise, engine version string.
|
||||
/// </summary>
|
||||
[EventScript("CompileFailure")]
|
||||
CompileFailure,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Game directory path, engine version string
|
||||
/// Parameters: Game directory path, engine version string.
|
||||
/// </summary>
|
||||
[EventScript("PostCompile")]
|
||||
CompileComplete,
|
||||
|
||||
/// <summary>
|
||||
/// No parameters
|
||||
/// No parameters.
|
||||
/// </summary>
|
||||
[EventScript("InstanceAutoUpdateStart")]
|
||||
InstanceAutoUpdateStart,
|
||||
|
||||
/// <summary>
|
||||
/// Parameters: Base sha, target sha, base reference, target reference, all conflicting files
|
||||
/// Parameters: Base sha, target sha, base reference, target reference, all conflicting files.
|
||||
/// </summary>
|
||||
[EventScript("RepoMergeConflict")]
|
||||
RepoMergeConflict,
|
||||
|
||||
/// <summary>
|
||||
/// No parameters
|
||||
/// No parameters.
|
||||
/// </summary>
|
||||
[EventScript("DeploymentComplete")]
|
||||
DeploymentComplete,
|
||||
@@ -139,31 +139,31 @@
|
||||
WorldPrime,
|
||||
|
||||
/// <summary>
|
||||
/// After DD has launched. Not the same as WatchdogLaunch. Parameters: PID of DreamDaemon
|
||||
/// After DD has launched. Not the same as WatchdogLaunch. Parameters: PID of DreamDaemon.
|
||||
/// </summary>
|
||||
[EventScript("DreamDaemonLaunch")]
|
||||
DreamDaemonLaunch,
|
||||
|
||||
/// <summary>
|
||||
/// After a single submodule update is performed. Parameters: Updated submodule name
|
||||
/// After a single submodule update is performed. Parameters: Updated submodule name.
|
||||
/// </summary>
|
||||
[EventScript("RepoSubmoduleUpdate")]
|
||||
RepoSubmoduleUpdate,
|
||||
|
||||
/// <summary>
|
||||
/// After CodeModifications are applied, before DreamMaker is run. Parameters: Game directory path, origin commit sha, engine version string
|
||||
/// After CodeModifications are applied, before DreamMaker is run. Parameters: Game directory path, origin commit sha, engine version string.
|
||||
/// </summary>
|
||||
[EventScript("PreDreamMaker")]
|
||||
PreDreamMaker,
|
||||
|
||||
/// <summary>
|
||||
/// Whenever a deployment folder is deleted from disk. Parameters: Game directory path
|
||||
/// Whenever a deployment folder is deleted from disk. Parameters: Game directory path.
|
||||
/// </summary>
|
||||
[EventScript("DeploymentCleanup")]
|
||||
DeploymentCleanup,
|
||||
|
||||
/// <summary>
|
||||
/// Whenever a deployment is about to be used by the game server. May fire multiple times per deployment. Parameters: Game directory path
|
||||
/// Whenever a deployment is about to be used by the game server. May fire multiple times per deployment. Parameters: Game directory path.
|
||||
/// </summary>
|
||||
[EventScript("DeploymentActivation")]
|
||||
DeploymentActivation,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
Startup,
|
||||
|
||||
/// <summary>
|
||||
/// DreamDaemon notifying the server is primed
|
||||
/// DreamDaemon notifying the server is primed.
|
||||
/// </summary>
|
||||
Prime,
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Tgstation.Server.Host.Components.Interop
|
||||
/// <summary>
|
||||
/// <see cref="JsonSerializerSettings"/> for use when communicating with the DMAPI.
|
||||
/// </summary>
|
||||
public static readonly JsonSerializerSettings SerializerSettings = new ()
|
||||
public static readonly JsonSerializerSettings SerializerSettings = new()
|
||||
{
|
||||
ContractResolver = new DefaultContractResolver
|
||||
{
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
|
||||
/// <param name="newInstanceName">The value of <see cref="NewInstanceName"/>.</param>
|
||||
/// <returns>The created <see cref="TopicParameters"/>.</returns>
|
||||
public static TopicParameters CreateInstanceRenamedTopicParameters(string newInstanceName)
|
||||
=> new (
|
||||
=> new(
|
||||
newInstanceName ?? throw new ArgumentNullException(nameof(newInstanceName)),
|
||||
TopicCommandType.InstanceRenamed);
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Tgstation.Server.Host.Components.Interop.Topic
|
||||
/// <param name="broadcastMessage">The value of <see cref="BroadcastMessage"/>.</param>
|
||||
/// <returns>The created <see cref="TopicParameters"/>.</returns>
|
||||
public static TopicParameters CreateBroadcastParameters(string broadcastMessage)
|
||||
=> new (
|
||||
=> new(
|
||||
broadcastMessage ?? throw new ArgumentNullException(nameof(broadcastMessage)),
|
||||
TopicCommandType.Broadcast);
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
public string Reference => libGitRepo.Head.FriendlyName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri Origin => new (libGitRepo.Network.Remotes.First().Url);
|
||||
public Uri Origin => new(libGitRepo.Network.Remotes.First().Url);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LibGit2Sharp.IRepository"/> for the <see cref="Repository"/>.
|
||||
|
||||
@@ -6,32 +6,32 @@
|
||||
enum ApiValidationStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// The DMAPI never contacted the server for validation
|
||||
/// The DMAPI never contacted the server for validation.
|
||||
/// </summary>
|
||||
NeverValidated,
|
||||
|
||||
/// <summary>
|
||||
/// The server was contacted for validation but it was never requested
|
||||
/// The server was contacted for validation but it was never requested.
|
||||
/// </summary>
|
||||
UnaskedValidationRequest,
|
||||
|
||||
/// <summary>
|
||||
/// The validation request was malformed
|
||||
/// 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"/>
|
||||
/// 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"/>
|
||||
/// 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"/>
|
||||
/// Valid API. The game must be run with a minimum security level of <see cref="Api.Models.DreamDaemonSecurity.Ultrasafe"/>.
|
||||
/// </summary>
|
||||
RequiresUltrasafe,
|
||||
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
public enum RebootState : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Run DreamDaemon's normal reboot process
|
||||
/// Run DreamDaemon's normal reboot process.
|
||||
/// </summary>
|
||||
Normal = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Shutdown DreamDaemon
|
||||
/// Shutdown DreamDaemon.
|
||||
/// </summary>
|
||||
Shutdown = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Restart the DreamDaemon process
|
||||
/// Restart the DreamDaemon process.
|
||||
/// </summary>
|
||||
Restart = 2,
|
||||
}
|
||||
|
||||
@@ -614,7 +614,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
DreamDaemonSecurity securityLevel,
|
||||
DreamDaemonVisibility visibility,
|
||||
bool apiValidateOnly)
|
||||
=> new (
|
||||
=> new(
|
||||
chatTrackingContext,
|
||||
dmbProvider,
|
||||
assemblyInformationProvider.Version,
|
||||
|
||||
@@ -6,22 +6,22 @@
|
||||
enum MonitorAction
|
||||
{
|
||||
/// <summary>
|
||||
/// The monitor should continue as normal
|
||||
/// 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
|
||||
/// The monitor should kill and restart both servers.
|
||||
/// </summary>
|
||||
Restart,
|
||||
|
||||
/// <summary>
|
||||
/// The monitor should stop checking actions for this iteration and continue its loop
|
||||
/// The monitor should stop checking actions for this iteration and continue its loop.
|
||||
/// </summary>
|
||||
Break,
|
||||
|
||||
|
||||
@@ -6,22 +6,22 @@
|
||||
enum MonitorActivationReason
|
||||
{
|
||||
/// <summary>
|
||||
/// The active server crashed or exited
|
||||
/// The active server crashed or exited.
|
||||
/// </summary>
|
||||
ActiveServerCrashed,
|
||||
|
||||
/// <summary>
|
||||
/// The active server called /world/Reboot()
|
||||
/// The active server called /world/Reboot().
|
||||
/// </summary>
|
||||
ActiveServerRebooted,
|
||||
|
||||
/// <summary>
|
||||
/// A new .dmb was deployed
|
||||
/// A new .dmb was deployed.
|
||||
/// </summary>
|
||||
NewDmbAvailable,
|
||||
|
||||
/// <summary>
|
||||
/// Server launch parameters were changed
|
||||
/// Server launch parameters were changed.
|
||||
/// </summary>
|
||||
ActiveLaunchParametersUpdated,
|
||||
|
||||
|
||||
@@ -6,27 +6,27 @@
|
||||
public enum DatabaseType
|
||||
{
|
||||
/// <summary>
|
||||
/// Use Microsoft SQL Server
|
||||
/// Use Microsoft SQL Server.
|
||||
/// </summary>
|
||||
SqlServer,
|
||||
|
||||
/// <summary>
|
||||
/// Use MySQL
|
||||
/// Use MySQL.
|
||||
/// </summary>
|
||||
MySql,
|
||||
|
||||
/// <summary>
|
||||
/// Use MariaDB
|
||||
/// Use MariaDB.
|
||||
/// </summary>
|
||||
MariaDB,
|
||||
|
||||
/// <summary>
|
||||
/// Use Sqlite
|
||||
/// Use Sqlite.
|
||||
/// </summary>
|
||||
Sqlite,
|
||||
|
||||
/// <summary>
|
||||
/// Use PostgresSql
|
||||
/// Use PostgresSql.
|
||||
/// </summary>
|
||||
PostgresSql,
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Tgstation.Server.Host.Extensions
|
||||
/// <summary>
|
||||
/// A <see cref="TaskCompletionSource"/> that never completes.
|
||||
/// </summary>
|
||||
static readonly TaskCompletionSource InfiniteTaskCompletionSource = new ();
|
||||
static readonly TaskCompletionSource InfiniteTaskCompletionSource = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Task"/> that never completes.
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace Tgstation.Server.Host.IO
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see cref="buffer"/> and its <see cref="Stream.Length"/>.</returns>
|
||||
async ValueTask<(MemoryStream, long)> GetResultInternal(CancellationToken cancellationToken)
|
||||
async ValueTask<(MemoryStream Stream, long StreamLength)> GetResultInternal(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!buffered)
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken))
|
||||
@@ -115,15 +115,15 @@ namespace Tgstation.Server.Host.IO
|
||||
await input.CopyToAsync(localBuffer, cancellationToken);
|
||||
localBuffer.Seek(0, SeekOrigin.Begin);
|
||||
buffered = true;
|
||||
return (localBuffer, localBuffer.Length);
|
||||
return (Stream: localBuffer, StreamLength: localBuffer.Length);
|
||||
}
|
||||
|
||||
lock (semaphore)
|
||||
{
|
||||
var localBuffer = buffer ?? throw new ObjectDisposedException(nameof(BufferedFileStreamProvider));
|
||||
return (
|
||||
localBuffer,
|
||||
localBuffer.Length);
|
||||
Stream: localBuffer,
|
||||
StreamLength: localBuffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ namespace Tgstation.Server.Host.IO
|
||||
TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public FileStream GetFileStream(string path, bool shareWrite) => new (
|
||||
public FileStream GetFileStream(string path, bool shareWrite) => new(
|
||||
ResolvePath(path),
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace Tgstation.Server.Host.Models
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public CompileJobResponse ToApi() => new ()
|
||||
public CompileJobResponse ToApi() => new()
|
||||
{
|
||||
DirectoryName = DirectoryName,
|
||||
DmeName = DmeName,
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Tgstation.Server.Host.Models
|
||||
public ICollection<Job> Jobs { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public InstanceResponse ToApi() => new ()
|
||||
public InstanceResponse ToApi() => new()
|
||||
{
|
||||
AutoUpdateInterval = AutoUpdateInterval,
|
||||
ConfigurationType = ConfigurationType,
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Tgstation.Server.Host.Models
|
||||
/// <returns>A new <see cref="Job"/> ready to be registered with the <see cref="Jobs.IJobService"/>.</returns>
|
||||
public static Job Create<TRight>(JobCode code, User startedBy, Api.Models.Instance instance, TRight cancelRight)
|
||||
where TRight : Enum
|
||||
=> new (
|
||||
=> new(
|
||||
code,
|
||||
startedBy,
|
||||
instance,
|
||||
@@ -57,7 +57,7 @@ namespace Tgstation.Server.Host.Models
|
||||
/// <param name="instance">The <see cref="Api.Models.Instance"/> used to generate the value of <see cref="Instance"/>.</param>
|
||||
/// <returns>A new <see cref="Job"/> ready to be registered with the <see cref="Jobs.IJobService"/>.</returns>
|
||||
public static Job Create(JobCode code, User startedBy, Api.Models.Instance instance)
|
||||
=> new (
|
||||
=> new(
|
||||
code,
|
||||
startedBy,
|
||||
instance,
|
||||
@@ -109,7 +109,7 @@ namespace Tgstation.Server.Host.Models
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public JobResponse ToApi() => new ()
|
||||
public JobResponse ToApi() => new()
|
||||
{
|
||||
Id = Id,
|
||||
JobCode = JobCode.Value,
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Tgstation.Server.Host.Models
|
||||
/// </summary>
|
||||
/// <param name="showUsers">If <see cref="UserGroupResponse.Users"/> should be populated.</param>
|
||||
/// <returns>A new <see cref="UserGroupResponse"/>.</returns>
|
||||
public UserGroupResponse ToApi(bool showUsers) => new ()
|
||||
public UserGroupResponse ToApi(bool showUsers) => new()
|
||||
{
|
||||
Id = Id,
|
||||
Name = Name,
|
||||
|
||||
@@ -17,10 +17,10 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
public override OAuthProvider Provider => OAuthProvider.Discord;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri TokenUrl => new ("https://discord.com/api/oauth2/token");
|
||||
protected override Uri TokenUrl => new("https://discord.com/api/oauth2/token");
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri UserInformationUrl => new ("https://discord.com/api/users/@me");
|
||||
protected override Uri UserInformationUrl => new("https://discord.com/api/users/@me");
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DiscordOAuthValidator"/> class.
|
||||
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override OAuthTokenRequest CreateTokenRequest(string code) => new (OAuthConfiguration, code, "identify");
|
||||
protected override OAuthTokenRequest CreateTokenRequest(string code) => new(OAuthConfiguration, code, "identify");
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string DecodeTokenPayload(dynamic responseJson) => responseJson.access_token;
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
/// Gets <see cref="JsonSerializerSettings"/> that should be used.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="JsonSerializerSettings"/> <see cref="object"/>.</returns>
|
||||
protected static JsonSerializerSettings SerializerSettings() => new ()
|
||||
protected static JsonSerializerSettings SerializerSettings() => new()
|
||||
{
|
||||
ContractResolver = new DefaultContractResolver
|
||||
{
|
||||
@@ -141,7 +141,7 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
|
||||
/// <inheritdoc />
|
||||
public OAuthProviderInfo GetProviderInfo()
|
||||
=> new ()
|
||||
=> new()
|
||||
{
|
||||
ClientId = OAuthConfiguration.ClientId,
|
||||
RedirectUri = OAuthConfiguration.RedirectUrl,
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
|
||||
/// <inheritdoc />
|
||||
public OAuthProviderInfo GetProviderInfo()
|
||||
=> new ()
|
||||
=> new()
|
||||
{
|
||||
ClientId = oAuthConfiguration.ClientId,
|
||||
RedirectUri = oAuthConfiguration.RedirectUrl,
|
||||
|
||||
@@ -17,10 +17,10 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
public override OAuthProvider Provider => OAuthProvider.InvisionCommunity;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri TokenUrl => new ($"{OAuthConfiguration.ServerUrl}/oauth/token/"); // This needs the trailing slash or it doesnt get the token. Do not remove.
|
||||
protected override Uri TokenUrl => new($"{OAuthConfiguration.ServerUrl}/oauth/token/"); // This needs the trailing slash or it doesnt get the token. Do not remove.
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri UserInformationUrl => new ($"{OAuthConfiguration.ServerUrl}/api/core/me");
|
||||
protected override Uri UserInformationUrl => new($"{OAuthConfiguration.ServerUrl}/api/core/me");
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="InvisionCommunityOAuthValidator"/> class.
|
||||
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override OAuthTokenRequest CreateTokenRequest(string code) => new (OAuthConfiguration, code, "profile");
|
||||
protected override OAuthTokenRequest CreateTokenRequest(string code) => new(OAuthConfiguration, code, "profile");
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string DecodeTokenPayload(dynamic responseJson) => responseJson.access_token;
|
||||
|
||||
@@ -17,10 +17,10 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
public override OAuthProvider Provider => OAuthProvider.Keycloak;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri TokenUrl => new ($"{BaseProtocolPath}/token");
|
||||
protected override Uri TokenUrl => new($"{BaseProtocolPath}/token");
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri UserInformationUrl => new ($"{BaseProtocolPath}/userinfo");
|
||||
protected override Uri UserInformationUrl => new($"{BaseProtocolPath}/userinfo");
|
||||
|
||||
/// <summary>
|
||||
/// Base path to the server's OAuth endpoint.
|
||||
@@ -42,7 +42,7 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override OAuthTokenRequest CreateTokenRequest(string code) => new (OAuthConfiguration, code, "openid");
|
||||
protected override OAuthTokenRequest CreateTokenRequest(string code) => new(OAuthConfiguration, code, "openid");
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string DecodeTokenPayload(dynamic responseJson) => responseJson.access_token;
|
||||
|
||||
@@ -17,10 +17,10 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
public override OAuthProvider Provider => OAuthProvider.TGForums;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri TokenUrl => new ("https://tgstation13.org/phpBB/app.php/tgapi/oauth/token");
|
||||
protected override Uri TokenUrl => new("https://tgstation13.org/phpBB/app.php/tgapi/oauth/token");
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Uri UserInformationUrl => new ("https://tgstation13.org/phpBB/app.php/tgapi/user/me");
|
||||
protected override Uri UserInformationUrl => new("https://tgstation13.org/phpBB/app.php/tgapi/user/me");
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TGForumsOAuthValidator"/> class.
|
||||
@@ -46,6 +46,6 @@ namespace Tgstation.Server.Host.Security.OAuth
|
||||
protected override string DecodeUserInformationPayload(dynamic responseJson) => responseJson.phpbb_username;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override OAuthTokenRequest CreateTokenRequest(string code) => new (OAuthConfiguration, code, "user");
|
||||
protected override OAuthTokenRequest CreateTokenRequest(string code) => new(OAuthConfiguration, code, "user");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Tgstation.Server.Host.Swarm
|
||||
/// </summary>
|
||||
static SwarmConstants()
|
||||
{
|
||||
SerializerSettings = new ()
|
||||
SerializerSettings = new()
|
||||
{
|
||||
ContractResolver = new DefaultContractResolver
|
||||
{
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace Tgstation.Server.Host.Swarm
|
||||
/// <summary>
|
||||
/// <see cref="Dictionary{TKey, TValue}"/> of <see cref="Api.Models.Internal.SwarmServer.Identifier"/>s to registration <see cref="Guid"/>s and when they were created.
|
||||
/// </summary>
|
||||
readonly Dictionary<string, (Guid, DateTimeOffset)> registrationIdsAndTimes;
|
||||
readonly Dictionary<string, (Guid RegistrationId, DateTimeOffset RegisteredAt)> registrationIdsAndTimes;
|
||||
|
||||
/// <summary>
|
||||
/// If the current server is the swarm controller.
|
||||
@@ -193,7 +193,7 @@ namespace Tgstation.Server.Host.Swarm
|
||||
serverHealthCheckCancellationTokenSource = new CancellationTokenSource();
|
||||
forceHealthCheckTcs = new TaskCompletionSource();
|
||||
if (swarmController)
|
||||
registrationIdsAndTimes = new ();
|
||||
registrationIdsAndTimes = new();
|
||||
|
||||
swarmServers = new List<SwarmServerResponse>
|
||||
{
|
||||
@@ -520,7 +520,7 @@ namespace Tgstation.Server.Host.Swarm
|
||||
{
|
||||
if (swarmController)
|
||||
lock (swarmServers)
|
||||
return registrationIdsAndTimes.Values.Any(x => x.Item1 == registrationId);
|
||||
return registrationIdsAndTimes.Values.Any(x => x.RegistrationId == registrationId);
|
||||
|
||||
if (registrationId != controllerRegistration)
|
||||
return false;
|
||||
@@ -549,9 +549,9 @@ namespace Tgstation.Server.Host.Swarm
|
||||
|
||||
lock (swarmServers)
|
||||
{
|
||||
if (registrationIdsAndTimes.Any(x => x.Value.Item1 == registrationId))
|
||||
if (registrationIdsAndTimes.Any(x => x.Value.RegistrationId == registrationId))
|
||||
{
|
||||
var preExistingRegistrationKvp = registrationIdsAndTimes.FirstOrDefault(x => x.Value.Item1 == registrationId);
|
||||
var preExistingRegistrationKvp = registrationIdsAndTimes.FirstOrDefault(x => x.Value.RegistrationId == registrationId);
|
||||
if (preExistingRegistrationKvp.Key == node.Identifier)
|
||||
{
|
||||
logger.LogWarning("Node {nodeId} has already registered!", node.Identifier);
|
||||
@@ -580,7 +580,7 @@ namespace Tgstation.Server.Host.Swarm
|
||||
Identifier = node.Identifier,
|
||||
Controller = false,
|
||||
});
|
||||
registrationIdsAndTimes.Add(node.Identifier, (registrationId, DateTimeOffset.UtcNow));
|
||||
registrationIdsAndTimes.Add(node.Identifier, (RegistrationId: registrationId, DateTimeOffset.UtcNow));
|
||||
}
|
||||
|
||||
logger.LogInformation("Registered node {nodeId} ({nodeIP}) with ID {registrationId}", node.Identifier, node.Address, registrationId);
|
||||
@@ -1139,7 +1139,7 @@ namespace Tgstation.Server.Host.Swarm
|
||||
currentSwarmServers
|
||||
.Where(node => !node.Controller
|
||||
&& registrationIdsAndTimes.TryGetValue(node.Identifier, out var registrationAndTime)
|
||||
&& registrationAndTime.Item2.AddMinutes(SwarmConstants.ControllerHealthCheckIntervalMinutes) < DateTimeOffset.UtcNow)
|
||||
&& registrationAndTime.RegisteredAt.AddMinutes(SwarmConstants.ControllerHealthCheckIntervalMinutes) < DateTimeOffset.UtcNow)
|
||||
.Select(HealthRequestForServer));
|
||||
|
||||
lock (swarmServers)
|
||||
@@ -1390,7 +1390,7 @@ namespace Tgstation.Server.Host.Swarm
|
||||
{
|
||||
lock (swarmServers)
|
||||
if (registrationIdsAndTimes.TryGetValue(swarmServer.Identifier, out var registrationIdAndTime))
|
||||
request.Headers.Add(SwarmConstants.RegistrationIdHeader, registrationIdAndTime.Item1.ToString());
|
||||
request.Headers.Add(SwarmConstants.RegistrationIdHeader, registrationIdAndTime.RegistrationId.ToString());
|
||||
}
|
||||
else if (controllerRegistration.HasValue)
|
||||
request.Headers.Add(SwarmConstants.RegistrationIdHeader, controllerRegistration.Value.ToString());
|
||||
@@ -1505,14 +1505,14 @@ namespace Tgstation.Server.Host.Swarm
|
||||
|
||||
lock (swarmServers)
|
||||
{
|
||||
var exists = registrationIdsAndTimes.Any(x => x.Value.Item1 == registrationId);
|
||||
var exists = registrationIdsAndTimes.Any(x => x.Value.RegistrationId == registrationId);
|
||||
if (!exists)
|
||||
{
|
||||
logger.LogWarning("A node that was to be looked up ({registrationId}) disappeared from our records!", registrationId);
|
||||
return null;
|
||||
}
|
||||
|
||||
return registrationIdsAndTimes.First(x => x.Value.Item1 == registrationId).Key;
|
||||
return registrationIdsAndTimes.First(x => x.Value.RegistrationId == registrationId).Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Tgstation.Server.Host.System
|
||||
public string VersionString { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ProductInfoHeaderValue ProductInfoHeaderValue => new (
|
||||
public ProductInfoHeaderValue ProductInfoHeaderValue => new(
|
||||
VersionPrefix,
|
||||
Version.ToString());
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Tgstation.Server.Host.System
|
||||
/// <summary>
|
||||
/// <see cref="ReaderWriterLockSlim"/> for <see cref="WithProcessLaunchExclusivity(Action)"/>.
|
||||
/// </summary>
|
||||
static readonly ReaderWriterLockSlim ExclusiveProcessLaunchLock = new ();
|
||||
static readonly ReaderWriterLockSlim ExclusiveProcessLaunchLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IProcessFeatures"/> for the <see cref="ProcessExecutor"/>.
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Tgstation.Server.Host.System
|
||||
try
|
||||
{
|
||||
var pointerChildHandlesList = GCHandle.ToIntPtr(gcChildhandlesList);
|
||||
NativeMethods.EnumWindowProc childProc = new (EnumWindow);
|
||||
NativeMethods.EnumWindowProc childProc = new(EnumWindow);
|
||||
NativeMethods.EnumChildWindows(mainWindow, childProc, pointerChildHandlesList);
|
||||
}
|
||||
finally
|
||||
@@ -113,11 +113,12 @@ namespace Tgstation.Server.Host.System
|
||||
|
||||
process.Lifetime.ContinueWith(
|
||||
x =>
|
||||
{
|
||||
logger.LogTrace("Unregistering process {0}...", process.Id);
|
||||
lock (registeredProcesses)
|
||||
registeredProcesses.Remove(process);
|
||||
}, TaskScheduler.Current);
|
||||
{
|
||||
logger.LogTrace("Unregistering process {pid}...", process.Id);
|
||||
lock (registeredProcesses)
|
||||
registeredProcesses.Remove(process);
|
||||
},
|
||||
TaskScheduler.Current);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -240,7 +240,7 @@ namespace Tgstation.Server.Host.Transfer
|
||||
/// Creates a new <see cref="FileTicketResponse"/>.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="FileTicketResponse"/>.</returns>
|
||||
FileTicketResponse CreateTicket() => new ()
|
||||
FileTicketResponse CreateTicket() => new()
|
||||
{
|
||||
FileTicket = cryptographySuite.GetSecureString(),
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Utils.GitHub
|
||||
/// <summary>
|
||||
/// Cache of created <see cref="GitHubClient"/>s and last used times, keyed by access token.
|
||||
/// </summary>
|
||||
readonly Dictionary<string, (GitHubClient, DateTimeOffset)> clientCache;
|
||||
readonly Dictionary<string, (GitHubClient Client, DateTimeOffset LastUsed)> clientCache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GitHubClientFactory"/> class.
|
||||
@@ -105,15 +105,15 @@ namespace Tgstation.Server.Host.Utils.GitHub
|
||||
if (accessToken != null)
|
||||
client.Credentials = new Credentials(accessToken);
|
||||
|
||||
clientCache.Add(cacheKey, (client, now));
|
||||
clientCache.Add(cacheKey, (Client: client, LastUsed: now));
|
||||
lastUsed = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogTrace("Cache hit for GitHubClient");
|
||||
client = tuple.Item1;
|
||||
lastUsed = tuple.Item2;
|
||||
tuple.Item2 = now;
|
||||
client = tuple.Client;
|
||||
lastUsed = tuple.LastUsed;
|
||||
tuple.LastUsed = now;
|
||||
}
|
||||
|
||||
// Prune the cache
|
||||
@@ -125,7 +125,7 @@ namespace Tgstation.Server.Host.Utils.GitHub
|
||||
continue; // save the hash lookup
|
||||
|
||||
tuple = clientCache[key];
|
||||
if (tuple.Item2 <= purgeAfter)
|
||||
if (tuple.LastUsed <= purgeAfter)
|
||||
{
|
||||
clientCache.Remove(key);
|
||||
++purgeCount;
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Tgstation.Server.Host.Utils.GitHub
|
||||
/// <param name="gitHubClient">The <see cref="IGitHubClient"/> for the <see cref="GitHubService"/>.</param>
|
||||
/// <returns>A new <see cref="GitHubService"/>.</returns>
|
||||
GitHubService CreateServiceImpl(IGitHubClient gitHubClient)
|
||||
=> new (
|
||||
=> new(
|
||||
gitHubClient,
|
||||
loggerFactory.CreateLogger<GitHubService>(),
|
||||
updatesConfiguration);
|
||||
|
||||
@@ -39,15 +39,25 @@ namespace Tgstation.Server.Host.Service.Tests
|
||||
var childStarted = false;
|
||||
ISignalChecker signalChecker = null;
|
||||
|
||||
mockWatchdog.Setup(x => x.RunAsync(false, It.IsNotNull<string[]>(), It.IsAny<CancellationToken>())).Callback((bool x, string[] _, CancellationToken token) =>
|
||||
var hostVersionTcs = new TaskCompletionSource<Version>();
|
||||
var hostLifetimeTcs = new TaskCompletionSource<bool>();
|
||||
|
||||
mockWatchdog.Setup(x => x.RunAsync(false, It.IsNotNull<string[]>(), It.IsAny<CancellationToken>())).Returns(async (bool x, string[] _, CancellationToken token) =>
|
||||
{
|
||||
hostVersionTcs.SetResult(typeof(ServerService).Assembly.GetName().Version);
|
||||
|
||||
cancellationToken = token;
|
||||
cancellationToken.Register(() => hostLifetimeTcs.SetResult(true));
|
||||
signalCheckerTask = signalChecker.CheckSignals(additionalArgs =>
|
||||
{
|
||||
childStarted = true;
|
||||
return (123, Task.CompletedTask);
|
||||
return (123, hostLifetimeTcs.Task);
|
||||
}, cancellationToken).AsTask();
|
||||
}).ReturnsAsync(true).Verifiable();
|
||||
|
||||
await signalCheckerTask;
|
||||
return true;
|
||||
}).Verifiable();
|
||||
mockWatchdog.SetupGet(x => x.InitialHostVersion).Returns(hostVersionTcs.Task);
|
||||
var mockWatchdogFactory = new Mock<IWatchdogFactory>();
|
||||
|
||||
mockWatchdogFactory.Setup(x => x.CreateWatchdog(It.IsNotNull<ISignalChecker>(), It.IsNotNull<ILoggerFactory>()))
|
||||
@@ -68,6 +78,7 @@ namespace Tgstation.Server.Host.Service.Tests
|
||||
|
||||
mockWatchdogFactory.VerifyAll();
|
||||
Assert.IsTrue(signalCheckerTask.IsCompleted);
|
||||
Assert.IsTrue(cancellationToken.IsCancellationRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user