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