tgstation-server 6.19.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
ServerFactory.cs
Go to the documentation of this file.
1using System;
2using System.IO.Abstractions;
3using System.Linq;
4using System.Threading;
5using System.Threading.Tasks;
6
7using Microsoft.AspNetCore.Hosting;
8using Microsoft.AspNetCore.Server.Kestrel.Core;
9using Microsoft.Extensions.Configuration;
10using Microsoft.Extensions.DependencyInjection;
11using Microsoft.Extensions.Hosting;
12using Microsoft.Extensions.Logging;
13
20
22{
27 {
31 public const string AppSettings = "appsettings";
32
34 public IIOManager IOManager { get; }
35
40
44 readonly IFileSystem fileSystem;
45
52 internal ServerFactory(
54 IIOManager ioManager,
55 IFileSystem fileSystem)
56 {
57 this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
58 IOManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
59 this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
60 }
61
63 // TODO: Decomplexify
64#pragma warning disable CA1506
65 public async ValueTask<IServer?> CreateServer(string[] args, string? updatePath, CancellationToken cancellationToken)
66 {
67 ArgumentNullException.ThrowIfNull(args);
68
69 // need to shove this arg in to disable config reloading unless a user specifically overrides it
70 if (!args.Any(arg => arg.Contains("hostBuilder:reloadConfigOnChange", StringComparison.OrdinalIgnoreCase))
71 && String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("hostBuilder__reloadConfigOnChange")))
72 {
73 var oldArgs = args;
74 args = new string[oldArgs.Length + 1];
75 Array.Copy(oldArgs, args, oldArgs.Length);
76 args[oldArgs.Length] = "--hostBuilder:reloadConfigOnChange=false";
77 }
78
79 const string AppSettingsRelocationKey = $"--{AppSettings}-base-path=";
80
81 var appsettingsRelativeBasePathArgument = args.FirstOrDefault(arg => arg.StartsWith(AppSettingsRelocationKey, StringComparison.Ordinal));
82 string basePath;
83 if (appsettingsRelativeBasePathArgument != null)
84 basePath = IOManager.ResolvePath(appsettingsRelativeBasePathArgument[AppSettingsRelocationKey.Length..]);
85 else
86 basePath = IOManager.ResolvePath();
87
88 // this is a massive bloody hack but I don't know a better way to do it
89 // It's needed for the setup wizard
90 Environment.SetEnvironmentVariable($"{InternalConfiguration.Section}__{nameof(InternalConfiguration.AppSettingsBasePath)}", basePath);
91
92 IHostBuilder CreateDefaultBuilder() => Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
93 .ConfigureAppConfiguration((context, builder) =>
94 {
95 builder.SetBasePath(basePath);
96
97 builder.AddYamlFile($"{AppSettings}.yml", optional: true, reloadOnChange: false)
98 .AddYamlFile($"{AppSettings}.{context.HostingEnvironment.EnvironmentName}.yml", optional: true, reloadOnChange: false);
99
100 // reorganize the builder so our yaml configs don't override the env/cmdline configs
101 // values obtained via debugger
102 var environmentJsonConfig = builder.Sources[2];
103 var envConfig = builder.Sources[3];
104
105 // CURSED
106 // https://github.com/dotnet/runtime/blob/30dc7e7aedb7aab085c7d9702afeae5bc5a43133/src/libraries/Microsoft.Extensions.Hosting/src/HostingHostBuilderExtensions.cs#L246-L249
107#if !NET8_0
108#error Validate this monstrosity works on current .NET
109#endif
110 IConfigurationSource? cmdLineConfig;
111 IConfigurationSource baseYmlConfig, environmentYmlConfig;
112 if (args.Length == 0)
113 {
114 cmdLineConfig = null;
115 baseYmlConfig = builder.Sources[4];
116 environmentYmlConfig = builder.Sources[5];
117 }
118 else
119 {
120 cmdLineConfig = builder.Sources[4];
121 baseYmlConfig = builder.Sources[5];
122 environmentYmlConfig = builder.Sources[6];
123 }
124
125 builder.Sources[2] = baseYmlConfig;
126 builder.Sources[3] = environmentJsonConfig;
127 builder.Sources[4] = environmentYmlConfig;
128 builder.Sources[5] = envConfig;
129
130 if (cmdLineConfig != null)
131 {
132 builder.Sources[6] = cmdLineConfig;
133 }
134 });
135
136 var setupWizardHostBuilder = CreateDefaultBuilder()
137 .UseSetupApplication(assemblyInformationProvider, IOManager);
138
139 IPostSetupServices postSetupServices;
140 using (var setupHost = setupWizardHostBuilder.Build())
141 {
142 ILogger<ServerFactory> logger = setupHost.Services.GetRequiredService<ILogger<ServerFactory>>();
143 postSetupServices = setupHost.Services.GetRequiredService<IPostSetupServices>();
144 await setupHost.RunAsync(cancellationToken);
145
146 if (postSetupServices.GeneralConfiguration.SetupWizardMode == SetupWizardMode.Only)
147 {
148 logger.LogInformation("Shutting down due to only running setup wizard.");
149 return null;
150 }
151
152 if (postSetupServices.ReloadRequired)
153 {
154 logger.LogInformation("TGS must restart to reload the updated configuration.");
155 return null;
156 }
157 }
158
159 var hostBuilder = CreateDefaultBuilder()
160 .ConfigureWebHost(webHostBuilder =>
161 webHostBuilder
162 .UseKestrel(kestrelOptions =>
163 {
164 var serverPortProvider = kestrelOptions.ApplicationServices.GetRequiredService<IServerPortProvider>();
165 kestrelOptions.ListenAnyIP(
166 serverPortProvider.HttpApiPort,
167 listenOptions => listenOptions.Protocols = HttpProtocols.Http1); // Can't use Http1And2 without TLS. Let the reverse proxy handle it
168
169 // with 515 we lost the ability to test this effectively. Just bump it slightly above the default and let the existing limit hold us back
170 kestrelOptions.Limits.MaxRequestLineSize = 8400;
171 })
172 .UseIIS()
173 .UseIISIntegration()
174 .UseApplication(assemblyInformationProvider, IOManager, postSetupServices, fileSystem)
175 .SuppressStatusMessages(true)
176 .UseShutdownTimeout(
177 TimeSpan.FromMinutes(
178 postSetupServices.GeneralConfiguration.RestartTimeoutMinutes)));
179
180 if (updatePath != null)
181 hostBuilder.UseContentRoot(
184
185 return new Server(IOManager, hostBuilder, updatePath);
186 }
187#pragma warning restore CA1506
188 }
189}
uint RestartTimeoutMinutes
The timeout minutes for restarting the server.
Implementation of IServerFactory.
IIOManager IOManager
The IIOManager for the IServerFactory.
readonly IFileSystem fileSystem
The IFileSystem for the ServerFactory.
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the ServerFactory.
async ValueTask< IServer?> CreateServer(string[] args, string? updatePath, CancellationToken cancellationToken)
Create a IServer.A ValueTask<TResult> resulting in a new IServer if it should be run,...
const string AppSettings
Name of the appsettings file.
Provides access to the server's HttpApiPort.
Interface for using filesystems.
Definition IIOManager.cs:14
string ResolvePath()
Retrieve the full path of the current working directory.
string GetDirectoryName(string path)
Gets the directory portion of a given path .
Set of objects needed to configure an Core.Application.
GeneralConfiguration GeneralConfiguration
The Configuration.GeneralConfiguration.
SetupWizardMode
Determines if the SetupWizard will run.