tgstation-server 6.19.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
DreamDaemonController.cs
Go to the documentation of this file.
1using System;
2using System.Linq;
7
11
25
26#pragma warning disable API1001 // Action method returns a success result without a corresponding ProducesResponseType. Somehow this happens ONLY IN THIS CONTROLLER???
27
29{
35 {
40
45
57 IDatabaseContext databaseContext,
58 IAuthenticationContext authenticationContext,
63 IApiHeadersProvider apiHeaders)
64 : base(
65 databaseContext,
66 authenticationContext,
67 logger,
69 apiHeaders)
70 {
73 }
74
81 [HttpPut]
85 => WithComponentInstance(async instance =>
86 {
87 if (instance.Watchdog.Status != WatchdogStatus.Offline)
88 return Conflict(new ErrorMessageResponse(ErrorCode.WatchdogRunning));
89
92 job,
93 (core, databaseContextFactory, paramJob, progressHandler, innerCt) => core!.Watchdog.Launch(innerCt),
94 cancellationToken);
95 return Accepted(job.ToApi());
96 });
97
105 [HttpGet]
106 [TgsAuthorize(DreamDaemonRights.ReadMetadata | DreamDaemonRights.ReadRevision)]
109 public ValueTask<IActionResult> Read(CancellationToken cancellationToken) => ReadImpl(null, false, cancellationToken);
110
117 [HttpDelete]
121 => WithComponentInstance(async instance =>
122 {
123 await instance.Watchdog.Terminate(false, cancellationToken);
124 return NoContent();
125 });
126
135 [HttpPost]
137 DreamDaemonRights.SetAutoStart
138 | DreamDaemonRights.SetPort
139 | DreamDaemonRights.SetSecurity
140 | DreamDaemonRights.SetWebClient
141 | DreamDaemonRights.SoftRestart
142 | DreamDaemonRights.SoftShutdown
143 | DreamDaemonRights.Start
144 | DreamDaemonRights.SetStartupTimeout
145 | DreamDaemonRights.SetHealthCheckInterval
146 | DreamDaemonRights.SetTopicTimeout
147 | DreamDaemonRights.SetAdditionalParameters
148 | DreamDaemonRights.SetVisibility
149 | DreamDaemonRights.SetProfiler
150 | DreamDaemonRights.SetLogOutput
151 | DreamDaemonRights.SetMapThreads
152 | DreamDaemonRights.BroadcastMessage
153 | DreamDaemonRights.SetMinidumps)]
156#pragma warning disable CA1502 // TODO: Decomplexify
157#pragma warning disable CA1506
159 {
160 ArgumentNullException.ThrowIfNull(model);
161
162 if (model.SoftShutdown == true && model.SoftRestart == true)
163 return BadRequest(new ErrorMessageResponse(ErrorCode.GameServerDoubleSoft));
164
165 // alias for changing DD settings
167 .Instances
168 .Where(x => x.Id == Instance.Id)
169 .Select(x => x.DreamDaemonSettings)
170 .FirstOrDefaultAsync(cancellationToken);
171
172 if (current == default)
173 return this.Gone();
174
175 if (model.Port.HasValue && model.Port.Value != current.Port!.Value)
176 {
177 Logger.LogTrace(
178 "Triggering port allocator for DD-I:{instanceId} because model port {modelPort} doesn't match DB port {dbPort}...",
179 Instance.Id,
180 model.Port,
181 current.Port);
184 model.Port.Value,
185 true,
186 cancellationToken);
187
188 if (verifiedPort != model.Port)
189 return Conflict(new ErrorMessageResponse(ErrorCode.PortNotAvailable));
190 }
191
193
195 {
197 var property = (PropertyInfo)memberSelectorExpression.Member;
198
199 var newVal = property.GetValue(model);
200 if (newVal == null)
201 return false;
202 if (!userRights.HasFlag(requiredRight) && property.GetValue(current) != newVal)
203 return true;
204
205 property.SetValue(current, newVal);
206 return false;
207 }
208
210 if (CheckModified(x => x.AllowWebClient, DreamDaemonRights.SetWebClient)
211 || CheckModified(x => x.AutoStart, DreamDaemonRights.SetAutoStart)
212 || CheckModified(x => x.Port, DreamDaemonRights.SetPort)
213 || CheckModified(x => x.OpenDreamTopicPort, DreamDaemonRights.SetPort)
214 || CheckModified(x => x.SecurityLevel, DreamDaemonRights.SetSecurity)
215 || CheckModified(x => x.Visibility, DreamDaemonRights.SetVisibility)
216 || (model.SoftRestart.HasValue && !ddRights.HasFlag(DreamDaemonRights.SoftRestart))
217 || (model.SoftShutdown.HasValue && !ddRights.HasFlag(DreamDaemonRights.SoftShutdown))
218 || (!String.IsNullOrWhiteSpace(model.BroadcastMessage) && !ddRights.HasFlag(DreamDaemonRights.BroadcastMessage))
219 || CheckModified(x => x.StartupTimeout, DreamDaemonRights.SetStartupTimeout)
220 || CheckModified(x => x.HealthCheckSeconds, DreamDaemonRights.SetHealthCheckInterval)
221 || CheckModified(x => x.DumpOnHealthCheckRestart, DreamDaemonRights.CreateDump)
222 || CheckModified(x => x.TopicRequestTimeout, DreamDaemonRights.SetTopicTimeout)
223 || CheckModified(x => x.AdditionalParameters, DreamDaemonRights.SetAdditionalParameters)
224 || CheckModified(x => x.StartProfiler, DreamDaemonRights.SetProfiler)
225 || CheckModified(x => x.LogOutput, DreamDaemonRights.SetLogOutput)
226 || CheckModified(x => x.MapThreads, DreamDaemonRights.SetMapThreads)
227 || CheckModified(x => x.Minidumps, DreamDaemonRights.SetMinidumps))
228 return Forbid();
229
231 async instance =>
232 {
233 var watchdog = instance.Watchdog;
234 if (!String.IsNullOrWhiteSpace(model.BroadcastMessage)
235 && !await watchdog.Broadcast(model.BroadcastMessage, cancellationToken))
236 return Conflict(new ErrorMessageResponse(ErrorCode.BroadcastFailure));
237
238 await DatabaseContext.Save(cancellationToken);
239
240 // run this second because current may be modified by it
241 // slight race condition with request cancellation, but I CANNOT be assed right now
242 var rebootRequired = await watchdog.ChangeSettings(current, cancellationToken);
243
244 var rebootState = watchdog.RebootState;
247 if (!oldSoftRestart && model.SoftRestart == true && watchdog.Status == WatchdogStatus.Online)
248 await watchdog.Restart(true, cancellationToken);
249 else if (!oldSoftShutdown && model.SoftShutdown == true)
250 await watchdog.Terminate(true, cancellationToken);
251 else if ((oldSoftRestart && model.SoftRestart == false) || (oldSoftShutdown && model.SoftShutdown == false))
252 await watchdog.ResetRebootState(cancellationToken);
253
254 return await ReadImpl(current, rebootRequired, cancellationToken);
255 });
256 }
257#pragma warning restore CA1506
258#pragma warning restore CA1502
259
266 [HttpPatch]
270 => WithComponentInstance(async instance =>
271 {
273
274 var watchdog = instance.Watchdog;
275
276 if (watchdog.Status == WatchdogStatus.Offline)
277 return Conflict(new ErrorMessageResponse(ErrorCode.WatchdogNotRunning));
278
280 job,
281 (core, paramJob, databaseContextFactory, progressReporter, ct) => core!.Watchdog.Restart(false, ct),
282 cancellationToken);
283 return Accepted(job.ToApi());
284 });
285
292 [HttpPatch(Routes.Diagnostics)]
293 [TgsAuthorize(DreamDaemonRights.CreateDump)]
296 => WithComponentInstance(async instance =>
297 {
299
300 var watchdog = instance.Watchdog;
301
302 if (watchdog.Status == WatchdogStatus.Offline)
303 return Conflict(new ErrorMessageResponse(ErrorCode.WatchdogNotRunning));
304
306 job,
307 (core, databaseContextFactory, paramJob, progressReporter, ct) => core!.Watchdog.CreateDump(ct),
308 cancellationToken);
309 return Accepted(job.ToApi());
310 });
311
319#pragma warning disable CA1502 // TODO: Decomplexify
321#pragma warning restore CA1502
322 => WithComponentInstance(async instance =>
323 {
324 var dd = instance.Watchdog;
325 var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadMetadata) != 0;
326 var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadRevision) != 0;
327
328 if (settings == null)
329 {
331 .Instances
332 .Where(x => x.Id == Instance.Id)
333 .Select(x => x.DreamDaemonSettings!)
334 .FirstOrDefaultAsync(cancellationToken);
335 if (settings == default)
336 return this.Gone();
337 }
338
340 if (metadata)
341 {
342 var alphaActive = dd.AlphaIsActive;
343 var llp = dd.LastLaunchParameters;
344 var rstate = dd.RebootState;
345 result.AutoStart = settings.AutoStart!.Value;
346 result.CurrentPort = llp?.Port!.Value;
347 result.CurrentTopicPort = llp?.OpenDreamTopicPort;
348 result.CurrentSecurity = llp?.SecurityLevel!.Value;
349 result.CurrentVisibility = llp?.Visibility!.Value;
350 result.CurrentAllowWebclient = llp?.AllowWebClient!.Value;
351 result.Port = settings.Port!.Value;
352 result.OpenDreamTopicPort = settings.OpenDreamTopicPort;
353 result.AllowWebClient = settings.AllowWebClient!.Value;
354
355 var firstIteration = true;
356 do
357 {
358 if (!firstIteration)
359 {
360 cancellationToken.ThrowIfCancellationRequested();
361 await Task.Yield();
362 }
363
364 firstIteration = false;
365 result.Status = dd.Status;
366 result.SessionId = dd.SessionId;
367 result.WorldIteration = dd.WorldIteration;
368 result.LaunchTime = dd.LaunchTime;
369 result.ClientCount = dd.ClientCount;
370 }
371 while (result.Status == WatchdogStatus.Online && !result.SessionId.HasValue); // this is the one invalid combo, it's not that racy
372
373 result.SecurityLevel = settings.SecurityLevel!.Value;
374 result.Visibility = settings.Visibility!.Value;
377 result.ImmediateMemoryUsage = dd.MemoryUsage;
378
379 if (rstate == RebootState.Normal && knownForcedReboot)
380 result.SoftRestart = true;
381
382 result.StartupTimeout = settings.StartupTimeout!.Value;
383 result.HealthCheckSeconds = settings.HealthCheckSeconds!.Value;
384 result.DumpOnHealthCheckRestart = settings.DumpOnHealthCheckRestart!.Value;
385 result.TopicRequestTimeout = settings.TopicRequestTimeout!.Value;
386 result.AdditionalParameters = settings.AdditionalParameters;
387 result.StartProfiler = settings.StartProfiler;
388 result.LogOutput = settings.LogOutput;
389 result.MapThreads = settings.MapThreads;
390 result.Minidumps = settings.Minidumps;
391 }
392
393 if (revision)
394 {
395 var latestCompileJob = await instance.LatestCompileJob();
399 ?.ToApi();
400 if (latestCompileJob?.Id != result.ActiveCompileJob?.Id)
402 }
403
404 return Json(result);
405 });
406 }
407}
virtual ? long Id
The ID of the entity.
Definition EntityId.cs:14
Metadata about a server instance.
Definition Instance.cs:9
DreamDaemonRights? DreamDaemonRights
The Rights.DreamDaemonRights of the InstancePermissionSet.
Represents an instance of BYOND's DreamDaemon game server. Create action starts the server....
Represents an error message returned by the server.
Represents a long running job on the server. Model is read-only, updates attempt to cancel the job.
Definition JobResponse.cs:7
Routes to a server actions.
Definition Routes.cs:9
const string DreamDaemon
The DreamDaemon controller.
Definition Routes.cs:63
const string Diagnostics
For accessing DD diagnostics.
Definition Routes.cs:68
ILogger< ApiController > Logger
The ILogger for the ApiController.
readonly IInstanceManager instanceManager
The IInstanceManager for the ComponentInterfacingController.
async ValueTask< IActionResult > WithComponentInstance(Func< IInstanceCore, ValueTask< IActionResult > > action, Models.Instance? instance=null)
Run a given action with the relevant IInstance.
ApiController for managing the DreamDaemonResponse.
ValueTask< IActionResult > Read(CancellationToken cancellationToken)
Get the watchdog status.
ValueTask< IActionResult > Restart(CancellationToken cancellationToken)
Creates a JobResponse to restart the Watchdog. It will not start if it wasn't already running.
readonly IPortAllocator portAllocator
The IPortAllocator for the DreamDaemonController.
ValueTask< IActionResult > Create(CancellationToken cancellationToken)
Launches the watchdog.
ValueTask< IActionResult > ReadImpl(DreamDaemonSettings? settings, bool knownForcedReboot, CancellationToken cancellationToken)
Implementation of Read(CancellationToken).
DreamDaemonController(IDatabaseContext databaseContext, IAuthenticationContext authenticationContext, ILogger< DreamDaemonController > logger, IInstanceManager instanceManager, IJobManager jobManager, IPortAllocator portAllocator, IApiHeadersProvider apiHeaders)
Initializes a new instance of the DreamDaemonController class.
ValueTask< IActionResult > CreateDump(CancellationToken cancellationToken)
Creates a JobResponse to generate a DreamDaemon process dump.
readonly IJobManager jobManager
The IJobManager for the DreamDaemonController.
async ValueTask< IActionResult > Update([FromBody] DreamDaemonRequest model, CancellationToken cancellationToken)
Update watchdog settings to be applied at next server reboot.
ValueTask< IActionResult > Delete(CancellationToken cancellationToken)
Stops the Watchdog if it's running.
ComponentInterfacingController for operations that require an instance.
Backend abstract implementation of IDatabaseContext.
DbSet< Instance > Instances
The Instances in the DatabaseContext.
Task Save(CancellationToken cancellationToken)
Saves changes made to the IDatabaseContext.A Task representing the running operation.
static Job Create(JobCode code, User? startedBy, Api.Models.Instance instance)
Creates a new job for registering in the Jobs.IJobService.
ulong GetRight(RightsType rightsType)
Get the value of a given rightsType .The value of rightsType . Note that if InstancePermissionSet is ...
Manages the runtime of Jobs.
ValueTask RegisterOperation(Job job, JobEntrypoint operation, CancellationToken cancellationToken)
Registers a given Job and begins running it.
For creating and accessing authentication contexts.
Gets unassigned ports for use by TGS.
ValueTask< ushort?> GetAvailablePort(ushort basePort, bool checkOne, CancellationToken cancellationToken)
Gets a port not currently in use by TGS.
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
Definition ErrorCode.cs:12
JobCode
The different types of Response.JobResponse.
Definition JobCode.cs:9
WatchdogStatus
The current status of the watchdog.
@ List
User may list files if the Models.Instance allows it.
RightsType
The type of rights a model uses.
Definition RightsType.cs:7
DreamDaemonRights
Rights for managing DreamDaemon.
RebootState
Represents the action to take when /world/Reboot() is called.
Definition RebootState.cs:7