diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs index 97d9aaaf5c..7e33d1178c 100644 --- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs +++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs @@ -35,6 +35,6 @@ namespace Tgstation.Server.Api.Models.Internal /// /// The DMAPI . /// - public Version DMApiVersion { get; set; + public Version DMApiVersion { get; set; } } } diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index 6156844e19..37cfe70a11 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -216,6 +216,8 @@ namespace Tgstation.Server.Host.Components.Deployment default: throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Session controller returned unexpected ApiValidationStatus: {0}", validationStatus)); } + + job.DMApiVersion = controller.DMApiVersion; } throw new JobException("DMAPI validation timed out!"); diff --git a/src/Tgstation.Server.Host/Components/InstanceFactory.cs b/src/Tgstation.Server.Host/Components/InstanceFactory.cs index ed3ceffea5..78366142ee 100644 --- a/src/Tgstation.Server.Host/Components/InstanceFactory.cs +++ b/src/Tgstation.Server.Host/Components/InstanceFactory.cs @@ -112,6 +112,11 @@ namespace Tgstation.Server.Host.Components /// readonly IRepositoryFactory repositoryFactory; + /// + /// The for the . + /// + readonly IServerPortProvider serverPortProvider; + /// /// Construct an /// @@ -133,6 +138,7 @@ namespace Tgstation.Server.Host.Components /// The value of /// The value of /// The value of . + /// The value of . public InstanceFactory( IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, @@ -151,7 +157,8 @@ namespace Tgstation.Server.Host.Components INetworkPromptReaper networkPromptReaper, IGitHubClientFactory gitHubClientFactory, IPlatformIdentifier platformIdentifier, - IRepositoryFactory repositoryFactory) + IRepositoryFactory repositoryFactory, + IServerPortProvider serverPortProvider) { this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory)); @@ -171,6 +178,7 @@ namespace Tgstation.Server.Host.Components this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory)); this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); this.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory)); + this.serverPortProvider = serverPortProvider ?? throw new ArgumentNullException(nameof(serverPortProvider)); } /// @@ -215,6 +223,7 @@ namespace Tgstation.Server.Host.Components networkPromptReaper, platformIdentifier, bridgeRegistrar, + serverPortProvider, loggerFactory, metadata.CloneMetadata()); diff --git a/src/Tgstation.Server.Host/Components/Interop/Runtime/RuntimeInformation.cs b/src/Tgstation.Server.Host/Components/Interop/Runtime/RuntimeInformation.cs index d5be4fddac..8b44d0f157 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Runtime/RuntimeInformation.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Runtime/RuntimeInformation.cs @@ -63,16 +63,17 @@ namespace Tgstation.Server.Host.Components.Interop.Runtime /// The value of . /// The value of . /// The value of . + /// The value of . public RuntimeInformation( IApplication application, ICryptographySuite cryptographySuite, - IServerPortProvider portProvider, IEnumerable testMerges, Api.Models.Instance instance, Api.Models.Internal.RevisionInformation revision, string channelsJson, string commandsJson, - DreamDaemonSecurity securityLevel) + DreamDaemonSecurity securityLevel, + ushort serverPort) { ServerVersion = application?.Version ?? throw new ArgumentNullException(nameof(application)); AccessIdentifier = cryptographySuite?.GetSecureString() ?? throw new ArgumentNullException(nameof(cryptographySuite)); @@ -82,6 +83,7 @@ namespace Tgstation.Server.Host.Components.Interop.Runtime ChatChannelsJson = channelsJson ?? throw new ArgumentNullException(nameof(channelsJson)); ChatChannelsJson = commandsJson ?? throw new ArgumentNullException(nameof(commandsJson)); SecurityLevel = securityLevel; + ServerPort = serverPort; } } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs index cbd5dc65f4..3d5e444d34 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs @@ -1,7 +1,6 @@ using Byond.TopicSender; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; using System; using System.Globalization; using System.Linq; @@ -76,6 +75,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IBridgeRegistrar bridgeRegistrar; + /// + /// The for the . + /// + readonly IServerPortProvider serverPortProvider; + /// /// The for the /// @@ -120,6 +124,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The value of /// The value of /// The value of . + /// The value of . /// The value of public SessionControllerFactory( IProcessExecutor processExecutor, @@ -132,6 +137,7 @@ namespace Tgstation.Server.Host.Components.Watchdog INetworkPromptReaper networkPromptReaper, IPlatformIdentifier platformIdentifier, IBridgeRegistrar bridgeRegistrar, + IServerPortProvider serverPortProvider, ILoggerFactory loggerFactory, Api.Models.Instance instance) { @@ -146,6 +152,7 @@ namespace Tgstation.Server.Host.Components.Watchdog this.networkPromptReaper = networkPromptReaper ?? throw new ArgumentNullException(nameof(networkPromptReaper)); this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier)); this.bridgeRegistrar = bridgeRegistrar ?? throw new ArgumentNullException(nameof(bridgeRegistrar)); + this.serverPortProvider = serverPortProvider ?? throw new ArgumentNullException(nameof(serverPortProvider)); this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } @@ -212,7 +219,8 @@ namespace Tgstation.Server.Host.Components.Watchdog revisionInfo, JsonFile("chat_channels"), JsonFile("chat_commands"), - securityLevelToUse); + securityLevelToUse, + await serverPortProvider.HttpApiPort.ConfigureAwait(false)); var interopJsonFile = JsonFile("interop"); diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 55021ccf9f..cc11929724 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -340,6 +341,7 @@ namespace Tgstation.Server.Host.Core services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(new ByondTopicSender { ReceiveTimeout = generalConfiguration.ByondTopicTimeout, @@ -373,6 +375,7 @@ namespace Tgstation.Server.Host.Core IApplicationBuilder applicationBuilder, IServerControl serverControl, ITokenFactory tokenFactory, + IServerPortProvider serverPortProvider, IOptions controlPanelConfigurationOptions, IOptions generalConfigurationOptions, ILogger logger) @@ -384,6 +387,12 @@ namespace Tgstation.Server.Host.Core this.tokenFactory = tokenFactory ?? throw new ArgumentNullException(nameof(tokenFactory)); + if (serverPortProvider == null) + throw new ArgumentNullException(nameof(serverPortProvider)); + + var addressFeature = applicationBuilder?.ServerFeatures.Get(); + serverPortProvider.Configure(addressFeature); + var controlPanelConfiguration = controlPanelConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(controlPanelConfigurationOptions)); var generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions)); diff --git a/src/Tgstation.Server.Host/Core/IServerPortProvider.cs b/src/Tgstation.Server.Host/Core/IServerPortProvider.cs new file mode 100644 index 0000000000..c1f98d4ab8 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/IServerPortProvider.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Hosting.Server.Features; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Core +{ + /// + /// Provides access to the server's . + /// + interface IServerPortProvider + { + /// + /// A resulting in the port the server listens on. + /// + Task HttpApiPort { get; } + + /// + /// Configures the . + /// + /// The to use. + void Configure(IServerAddressesFeature addressFeature); + } +} diff --git a/src/Tgstation.Server.Host/Core/ServerPortProivder.cs b/src/Tgstation.Server.Host/Core/ServerPortProivder.cs new file mode 100644 index 0000000000..cbc90ebc03 --- /dev/null +++ b/src/Tgstation.Server.Host/Core/ServerPortProivder.cs @@ -0,0 +1,65 @@ +using Microsoft.AspNetCore.Hosting.Server.Features; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Tgstation.Server.Host.Core +{ + /// + sealed class ServerPortProivder : IServerPortProvider + { + /// + public Task HttpApiPort => taskCompletionSource.Task; + + /// + /// Backing field for / + /// + readonly TaskCompletionSource taskCompletionSource; + + /// + /// In + /// + public ServerPortProivder() + { + taskCompletionSource = new TaskCompletionSource(); + } + + /// + public void Configure(IServerAddressesFeature addressFeature) + { + if (addressFeature == null) + throw new ArgumentNullException(nameof(addressFeature)); + + var enumerator = addressFeature.Addresses.Select(GetPortFromAddress); + var newPort = enumerator.FirstOrDefault(x => x.HasValue); + + if(!newPort.HasValue) + throw new InvalidOperationException("At least one plain HTTP endpoint must be configured. Neded for BYOND -> Server communications!"); + + if (!addressFeature.Addresses.Select(GetPortFromAddress).All(x => !x.HasValue || x == newPort)) + throw new InvalidOperationException("All configured HTTP server addresses must use the same port!"); + + // Will fail if set twice + taskCompletionSource.SetResult(newPort.Value); + } + + /// + /// Convert a given to its port. + /// + /// The address . + /// The parsed port. + static ushort? GetPortFromAddress(string address) + { + var splits = address.Split(":", StringSplitOptions.RemoveEmptyEntries); + if (splits.First().Equals("https", StringComparison.OrdinalIgnoreCase)) + return null; + + var portString = splits.Last(); + portString = portString.TrimEnd('/'); + if (UInt16.TryParse(portString, out var result)) + return result; + + return null; + } + } +}