tgstation-server 6.19.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
OpenDreamInstaller.cs
Go to the documentation of this file.
1using System;
2using System.Linq;
3using System.Net.Http;
4using System.Threading;
5using System.Threading.Tasks;
6
7using Microsoft.Extensions.Logging;
8using Microsoft.Extensions.Options;
9
18
20{
25 {
29 const string BinDir = "bin";
30
34 const string ServerDir = "server";
35
39 const string InstallationCompilerDirectory = "compiler";
40
44 const string InstallationSourceSubDirectory = "TgsSourceSubdir";
45
47 protected override EngineType TargetEngineType => EngineType.OpenDream;
48
53
57 protected IOptionsMonitor<GeneralConfiguration> GeneralConfigurationOptions { get; }
58
62 protected IOptionsMonitor<SessionConfiguration> SessionConfigurationOptions { get; }
63
68
73
78
82 readonly IHttpClientFactory httpClientFactory;
83
97 IIOManager ioManager,
98 ILogger<OpenDreamInstaller> logger,
100 IProcessExecutor processExecutor,
103 IHttpClientFactory httpClientFactory,
104 IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions,
105 IOptionsMonitor<SessionConfiguration> sessionConfigurationOptions)
106 : base(ioManager, logger)
107 {
108 this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
109 ProcessExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
110 this.repositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
111 this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
112 this.httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
113 GeneralConfigurationOptions = generalConfigurationOptions ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
114 SessionConfigurationOptions = sessionConfigurationOptions ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions));
115 }
116
118 public sealed override Task CleanCache(CancellationToken cancellationToken) => Task.CompletedTask;
119
121 public sealed override async ValueTask<IEngineInstallation> GetInstallation(EngineVersion version, string path, Task installationTask, CancellationToken cancellationToken)
122 {
123 CheckVersionValidity(version);
124 GetExecutablePaths(path, out var serverExePath, out var compilerExePath);
125
126 var dotnetPath = (await DotnetHelper.GetDotnetPath(platformIdentifier, IOManager, cancellationToken))
127 ?? throw new JobException("Failed to find dotnet path!");
128 return new OpenDreamInstallation(
132 dotnetPath,
133 serverExePath,
134 compilerExePath,
135 installationTask,
136 version);
137 }
138
140 public sealed override async ValueTask<IEngineInstallationData> DownloadVersion(EngineVersion version, JobProgressReporter jobProgressReporter, CancellationToken cancellationToken)
141 {
142 CheckVersionValidity(version);
143 ArgumentNullException.ThrowIfNull(jobProgressReporter);
144
145 // get a lock on a system wide OD repo
146 Logger.LogTrace("Cloning OD repo...");
147
148 var progressSection1 = jobProgressReporter.CreateSection("Updating OpenDream git repository", 0.5f);
149 IRepository? repo;
150 var generalConfig = GeneralConfigurationOptions.CurrentValue;
151 try
152 {
154 generalConfig.OpenDreamGitUrl,
155 null,
156 null,
157 null,
158 progressSection1,
159 true,
160 cancellationToken);
161 }
162 catch
163 {
164 progressSection1.Dispose();
165 throw;
166 }
167
168 try
169 {
170 if (repo == null)
171 {
172 Logger.LogTrace("OD repo seems to already exist, attempting load and fetch...");
173 repo = await repositoryManager.LoadRepository(cancellationToken);
174 if (repo == null)
175 throw new JobException("Can't load OpenDream repository! Please delete cache from disk!");
176
177 await repo!.FetchOrigin(
178 progressSection1,
179 null,
180 null,
181 false,
182 cancellationToken);
183 }
184
185 progressSection1.Dispose();
186 progressSection1 = null;
187
188 using (var progressSection2 = jobProgressReporter.CreateSection("Checking out OpenDream version", 0.5f))
189 {
190 var committish = version.SourceSHA
191 ?? $"{generalConfig.OpenDreamGitTagPrefix}{version.Version!.Semver()}";
192
193 await repo.CheckoutObject(
194 committish,
195 null,
196 null,
197 true,
198 false,
199 progressSection2,
200 cancellationToken);
201 }
202
203 if (!await repo.CommittishIsParent("tgs-min-compat", cancellationToken))
204 throw new JobException(ErrorCode.OpenDreamTooOld);
205
207 }
208 catch
209 {
210 repo?.Dispose();
211 throw;
212 }
213 finally
214 {
215 progressSection1?.Dispose();
216 }
217 }
218
220 public override ValueTask UpgradeInstallation(EngineVersion version, string path, CancellationToken cancellationToken)
221 {
222 CheckVersionValidity(version);
223 ArgumentNullException.ThrowIfNull(path);
224 return ValueTask.CompletedTask;
225 }
226
228 public override ValueTask TrustDmbPath(EngineVersion engineVersion, string fullDmbPath, CancellationToken cancellationToken)
229 {
230 ArgumentNullException.ThrowIfNull(engineVersion);
231 ArgumentNullException.ThrowIfNull(fullDmbPath);
232
233 Logger.LogTrace("TrustDmbPath is a no-op: {path}", fullDmbPath);
234 return ValueTask.CompletedTask;
235 }
236
238 protected override async ValueTask InstallImpl(EngineVersion version, string installPath, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
239 {
240 var sourcePath = IOManager.ConcatPath(installPath, InstallationSourceSubDirectory);
241
242 if (!await IOManager.DirectoryExists(sourcePath, cancellationToken))
243 {
244 // a zip install that didn't come from us?
245 // we want to use the bin dir, so put everything where we expect
246 Logger.LogDebug("Correcting extraction location...");
247 var dirsTask = IOManager.GetDirectories(installPath, cancellationToken);
248 var filesTask = IOManager.GetFiles(installPath, cancellationToken);
249 var dirCreateTask = IOManager.CreateDirectory(sourcePath, cancellationToken);
250
251 await Task.WhenAll(dirsTask, filesTask, dirCreateTask);
252
253 var dirsMoveTasks = dirsTask
254 .Result
255 .Select(
256 dirPath => IOManager.MoveDirectory(
257 dirPath,
259 sourcePath,
260 IOManager.GetFileName(dirPath)),
261 cancellationToken));
262 var filesMoveTask = filesTask
263 .Result
264 .Select(
265 filePath => IOManager.MoveFile(
266 filePath,
268 sourcePath,
269 IOManager.GetFileName(filePath)),
270 cancellationToken));
271
272 await Task.WhenAll(dirsMoveTasks.Concat(filesMoveTask));
273 }
274
275 var dotnetPath = (await DotnetHelper.GetDotnetPath(platformIdentifier, IOManager, cancellationToken))
276 ?? throw new JobException(ErrorCode.OpenDreamCantFindDotnet);
277 const string DeployDir = "tgs_deploy";
278 int? buildExitCode = null;
280 async shortenedPath =>
281 {
282 var shortenedDeployPath = IOManager.ConcatPath(shortenedPath, DeployDir);
283 var generalConfig = GeneralConfigurationOptions.CurrentValue;
284 await using var buildProcess = await ProcessExecutor.LaunchProcess(
285 dotnetPath,
286 shortenedPath,
287 $"run -c Release --project OpenDreamPackageTool -- --tgs -o {shortenedDeployPath}",
288 cancellationToken,
289 null,
290 null,
291 !generalConfig.OpenDreamSuppressInstallOutput,
292 !generalConfig.OpenDreamSuppressInstallOutput);
293
294 if (deploymentPipelineProcesses && SessionConfigurationOptions.CurrentValue.LowPriorityDeploymentProcesses)
295 buildProcess.AdjustPriority(false);
296
297 using (cancellationToken.Register(() => buildProcess.Terminate()))
298 buildExitCode = await buildProcess.Lifetime;
299
300 string? output;
301 if (!GeneralConfigurationOptions.CurrentValue.OpenDreamSuppressInstallOutput)
302 {
303 var buildOutputTask = buildProcess.GetCombinedOutput(cancellationToken);
304 if (!buildOutputTask.IsCompleted)
305 Logger.LogTrace("OD build complete, waiting for output...");
306 output = await buildOutputTask;
307 }
308 else
309 output = "<Build output suppressed by configuration due to not being immediately available>";
310
311 Logger.LogDebug(
312 "OpenDream build exited with code {exitCode}:{newLine}{output}",
313 buildExitCode,
314 Environment.NewLine,
315 output);
316 },
317 sourcePath,
318 cancellationToken);
319
320 if (buildExitCode != 0)
321 throw new JobException("OpenDream build failed!");
322
323 var deployPath = IOManager.ConcatPath(sourcePath, DeployDir);
324 async ValueTask MoveDirs()
325 {
326 var dirs = await IOManager.GetDirectories(deployPath, cancellationToken);
327 await Task.WhenAll(
328 dirs.Select(
330 dir,
332 installPath,
334 cancellationToken)));
335 }
336
337 async ValueTask MoveFiles()
338 {
339 var files = await IOManager.GetFiles(deployPath, cancellationToken);
340 await Task.WhenAll(
341 files.Select(
342 file => IOManager.MoveFile(
343 file,
345 installPath,
346 IOManager.GetFileName(file)),
347 cancellationToken)));
348 }
349
350 var dirsMoveTask = MoveDirs();
351 var outputFilesMoveTask = MoveFiles();
352 await ValueTaskExtensions.WhenAll(dirsMoveTask, outputFilesMoveTask);
353 await IOManager.DeleteDirectory(sourcePath, cancellationToken);
354 }
355
363 protected virtual ValueTask HandleExtremelyLongPathOperation(
364 Func<string, ValueTask> shortenedPathOperation,
365 string originalPath,
366 CancellationToken cancellationToken)
367 => shortenedPathOperation(originalPath); // based god linux has no such weakness
368
375 protected void GetExecutablePaths(string installationPath, out string serverExePath, out string compilerExePath)
376 {
377 const string DllExtension = ".dll";
378
379 serverExePath = IOManager.ConcatPath(
380 installationPath,
381 BinDir,
382 ServerDir,
383 $"Robust.Server{DllExtension}");
384
385 compilerExePath = IOManager.ConcatPath(
386 installationPath,
387 BinDir,
389 $"DMCompiler{DllExtension}");
390 }
391 }
392}
Information about an engine installation.
Extension methods for the ValueTask and ValueTask<TResult> classes.
static async ValueTask WhenAll(IEnumerable< ValueTask > tasks)
Fully await a given list of tasks .
void CheckVersionValidity(EngineVersion version)
Check that a given version is of type EngineType.Byond.
IIOManager IOManager
Gets the IIOManager for the EngineInstallerBase.
ILogger< EngineInstallerBase > Logger
Gets the ILogger for the EngineInstallerBase.
Implementation of IEngineInstallation for EngineType.OpenDream.
Implementation of IEngineInstaller for EngineType.OpenDream.
void GetExecutablePaths(string installationPath, out string serverExePath, out string compilerExePath)
Gets the paths to the server and client executables.
IOptionsMonitor< SessionConfiguration > SessionConfigurationOptions
The Configuration.SessionConfiguration for the OpenDreamInstaller.
OpenDreamInstaller(IIOManager ioManager, ILogger< OpenDreamInstaller > logger, IPlatformIdentifier platformIdentifier, IProcessExecutor processExecutor, IRepositoryManager repositoryManager, IAsyncDelayer asyncDelayer, IHttpClientFactory httpClientFactory, IOptionsMonitor< GeneralConfiguration > generalConfigurationOptions, IOptionsMonitor< SessionConfiguration > sessionConfigurationOptions)
Initializes a new instance of the OpenDreamInstaller class.
readonly IPlatformIdentifier platformIdentifier
The IPlatformIdentifier for the OpenDreamInstaller.
readonly IRepositoryManager repositoryManager
The IRepositoryManager for the OpenDream repository.
override ValueTask TrustDmbPath(EngineVersion engineVersion, string fullDmbPath, CancellationToken cancellationToken)
Add a given fullDmbPath to the trusted DMBs list in BYOND's config.A ValueTask representing the runn...
const string ServerDir
The OD server directory name.
override async ValueTask InstallImpl(EngineVersion version, string installPath, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
override async ValueTask< IEngineInstallation > GetInstallation(EngineVersion version, string path, Task installationTask, CancellationToken cancellationToken)
Creates an IEngineInstallation for a given version .A ValueTask<TResult> resulting in a new IEngineIn...
override async ValueTask< IEngineInstallationData > DownloadVersion(EngineVersion version, JobProgressReporter jobProgressReporter, CancellationToken cancellationToken)
Download a given engine version .A ValueTask<TResult> resulting in the IEngineInstallationData for th...
override Task CleanCache(CancellationToken cancellationToken)
Attempts to cleans the engine's cache folder for the system.A Task representing the running operation...
const string InstallationCompilerDirectory
The name of the subdirectory in an installation's BinDir used to store the compiler binaries.
readonly IHttpClientFactory httpClientFactory
The IHttpClientFactory for the OpenDreamInstaller.
const string InstallationSourceSubDirectory
The name of the subdirectory used for the RepositoryEngineInstallationData's copy.
override ValueTask UpgradeInstallation(EngineVersion version, string path, CancellationToken cancellationToken)
Does actions necessary to get upgrade a version installed by a previous version of TGS....
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the OpenDreamInstaller.
virtual ValueTask HandleExtremelyLongPathOperation(Func< string, ValueTask > shortenedPathOperation, string originalPath, CancellationToken cancellationToken)
Perform an operation on a very long path.
IOptionsMonitor< GeneralConfiguration > GeneralConfigurationOptions
The GeneralConfigurationOptions for the OpenDreamInstaller.
Operation exceptions thrown from the context of a Models.Job.
JobProgressReporter CreateSection(string? newStageName, double percentage)
Create a subsection of the JobProgressReporter with its optional own stage name.
Helper methods for working with the dotnet executable.
static async ValueTask< string?> GetDotnetPath(IPlatformIdentifier platformIdentifier, IIOManager ioManager, CancellationToken cancellationToken)
Locate a dotnet executable to use.
async ValueTask< IProcess > LaunchProcess(string fileName, string workingDirectory, string arguments, CancellationToken cancellationToken, IReadOnlyDictionary< string, string >? environment, string? fileRedirect, bool readStandardHandles, bool noShellExecute)
Launch a IProcess.A ValueTask<TResult> resulting in the new IProcess.
Represents an on-disk git repository.
Task< bool > CommittishIsParent(string committish, CancellationToken cancellationToken)
Check if a given committish is a parent of the current Head.
ValueTask CheckoutObject(string committish, string? username, string? password, bool updateSubmodules, bool moveCurrentReference, JobProgressReporter progressReporter, CancellationToken cancellationToken)
Checks out a given committish .
ValueTask FetchOrigin(JobProgressReporter progressReporter, string? username, string? password, bool deploymentPipeline, CancellationToken cancellationToken)
Fetch commits from the origin repository.
ValueTask< IRepository?> CloneRepository(Uri url, string? initialBranch, string? username, string? password, JobProgressReporter progressReporter, bool recurseSubmodules, CancellationToken cancellationToken)
Clone the repository at url .
ValueTask< IRepository?> LoadRepository(CancellationToken cancellationToken)
Attempt to load the IRepository from the default location.
Interface for using filesystems.
Definition IIOManager.cs:14
Task< IReadOnlyList< string > > GetFiles(string path, CancellationToken cancellationToken)
Returns full file names in a given path .
string GetFileName(string path)
Gets the file name portion of a path .
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
Task MoveFile(string source, string destination, CancellationToken cancellationToken)
Moves a file at source to destination .
Task< IReadOnlyList< string > > GetDirectories(string path, CancellationToken cancellationToken)
Returns full directory names in a given path .
Task CreateDirectory(string path, CancellationToken cancellationToken)
Create a directory at path .
IIOManager CreateResolverForSubdirectory(string subdirectoryPath)
Create a new IIOManager that resolves paths to the specified subdirectoryPath .
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
Task MoveDirectory(string source, string destination, CancellationToken cancellationToken)
Moves a directory at source to destination .
Task< bool > DirectoryExists(string path, CancellationToken cancellationToken)
Check that the directory at path exists.
For identifying the current platform.
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
Definition ErrorCode.cs:12
EngineType
The type of engine the codebase is using.
Definition EngineType.cs:7