tgstation-server 6.12.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),
95 return Accepted(job.ToApi());
96 });
97
105 [HttpGet]
106 [TgsAuthorize(DreamDaemonRights.ReadMetadata | DreamDaemonRights.ReadRevision)]
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 .AsQueryable()
169 .Where(x => x.Id == Instance.Id)
170 .Select(x => x.DreamDaemonSettings)
171 .FirstOrDefaultAsync(cancellationToken);
172
173 if (current == default)
174 return this.Gone();
175
176 if (model.Port.HasValue && model.Port.Value != current.Port!.Value)
177 {
178 Logger.LogTrace(
179 "Triggering port allocator for DD-I:{instanceId} because model port {modelPort} doesn't match DB port {dbPort}...",
180 Instance.Id,
181 model.Port,
182 current.Port);
185 model.Port.Value,
186 true,
188
189 if (verifiedPort != model.Port)
190 return Conflict(new ErrorMessageResponse(ErrorCode.PortNotAvailable));
191 }
192
194
196 {
198 var property = (PropertyInfo)memberSelectorExpression.Member;
199
200 var newVal = property.GetValue(model);
201 if (newVal == null)
202 return false;
203 if (!userRights.HasFlag(requiredRight) && property.GetValue(current) != newVal)
204 return true;
205
206 property.SetValue(current, newVal);
207 return false;
208 }
209
211 if (CheckModified(x => x.AllowWebClient, DreamDaemonRights.SetWebClient)
212 || CheckModified(x => x.AutoStart, DreamDaemonRights.SetAutoStart)
213 || CheckModified(x => x.Port, DreamDaemonRights.SetPort)
214 || CheckModified(x => x.OpenDreamTopicPort, DreamDaemonRights.SetPort)
215 || CheckModified(x => x.SecurityLevel, DreamDaemonRights.SetSecurity)
216 || CheckModified(x => x.Visibility, DreamDaemonRights.SetVisibility)
217 || (model.SoftRestart.HasValue && !ddRights.HasFlag(DreamDaemonRights.SoftRestart))
218 || (model.SoftShutdown.HasValue && !ddRights.HasFlag(DreamDaemonRights.SoftShutdown))
219 || (!String.IsNullOrWhiteSpace(model.BroadcastMessage) && !ddRights.HasFlag(DreamDaemonRights.BroadcastMessage))
220 || CheckModified(x => x.StartupTimeout, DreamDaemonRights.SetStartupTimeout)
221 || CheckModified(x => x.HealthCheckSeconds, DreamDaemonRights.SetHealthCheckInterval)
222 || CheckModified(x => x.DumpOnHealthCheckRestart, DreamDaemonRights.CreateDump)
223 || CheckModified(x => x.TopicRequestTimeout, DreamDaemonRights.SetTopicTimeout)
224 || CheckModified(x => x.AdditionalParameters, DreamDaemonRights.SetAdditionalParameters)
225 || CheckModified(x => x.StartProfiler, DreamDaemonRights.SetProfiler)
226 || CheckModified(x => x.LogOutput, DreamDaemonRights.SetLogOutput)
227 || CheckModified(x => x.MapThreads, DreamDaemonRights.SetMapThreads)
228 || CheckModified(x => x.Minidumps, DreamDaemonRights.SetMinidumps))
229 return Forbid();
230
232 async instance =>
233 {
234 var watchdog = instance.Watchdog;
235 if (!String.IsNullOrWhiteSpace(model.BroadcastMessage)
236 && !await watchdog.Broadcast(model.BroadcastMessage, cancellationToken))
237 return Conflict(new ErrorMessageResponse(ErrorCode.BroadcastFailure));
238
240
241 // run this second because current may be modified by it
242 // slight race condition with request cancellation, but I CANNOT be assed right now
243 var rebootRequired = await watchdog.ChangeSettings(current, cancellationToken);
244
245 var rebootState = watchdog.RebootState;
248 if (!oldSoftRestart && model.SoftRestart == true && watchdog.Status == WatchdogStatus.Online)
249 await watchdog.Restart(true, cancellationToken);
250 else if (!oldSoftShutdown && model.SoftShutdown == true)
251 await watchdog.Terminate(true, cancellationToken);
252 else if ((oldSoftRestart && model.SoftRestart == false) || (oldSoftShutdown && model.SoftShutdown == false))
253 await watchdog.ResetRebootState(cancellationToken);
254
256 });
257 }
258#pragma warning restore CA1506
259#pragma warning restore CA1502
260
267 [HttpPatch]
271 => WithComponentInstance(async instance =>
272 {
274
275 var watchdog = instance.Watchdog;
276
277 if (watchdog.Status == WatchdogStatus.Offline)
278 return Conflict(new ErrorMessageResponse(ErrorCode.WatchdogNotRunning));
279
281 job,
282 (core, paramJob, databaseContextFactory, progressReporter, ct) => core!.Watchdog.Restart(false, ct),
284 return Accepted(job.ToApi());
285 });
286
293 [HttpPatch(Routes.Diagnostics)]
294 [TgsAuthorize(DreamDaemonRights.CreateDump)]
297 => WithComponentInstance(async instance =>
298 {
300
301 var watchdog = instance.Watchdog;
302
303 if (watchdog.Status == WatchdogStatus.Offline)
304 return Conflict(new ErrorMessageResponse(ErrorCode.WatchdogNotRunning));
305
307 job,
308 (core, databaseContextFactory, paramJob, progressReporter, ct) => core!.Watchdog.CreateDump(ct),
310 return Accepted(job.ToApi());
311 });
312
320#pragma warning disable CA1502 // TODO: Decomplexify
322#pragma warning restore CA1502
323 => WithComponentInstance(async instance =>
324 {
325 var dd = instance.Watchdog;
326 var metadata = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadMetadata) != 0;
327 var revision = (AuthenticationContext.GetRight(RightsType.DreamDaemon) & (ulong)DreamDaemonRights.ReadRevision) != 0;
328
329 if (settings == null)
330 {
332 .Instances
333 .AsQueryable()
334 .Where(x => x.Id == Instance.Id)
335 .Select(x => x.DreamDaemonSettings!)
336 .FirstOrDefaultAsync(cancellationToken);
337 if (settings == default)
338 return this.Gone();
339 }
340
342 if (metadata)
343 {
344 var alphaActive = dd.AlphaIsActive;
345 var llp = dd.LastLaunchParameters;
346 var rstate = dd.RebootState;
347 result.AutoStart = settings.AutoStart!.Value;
348 result.CurrentPort = llp?.Port!.Value;
349 result.CurrentTopicPort = llp?.OpenDreamTopicPort;
350 result.CurrentSecurity = llp?.SecurityLevel!.Value;
351 result.CurrentVisibility = llp?.Visibility!.Value;
352 result.CurrentAllowWebclient = llp?.AllowWebClient!.Value;
353 result.Port = settings.Port!.Value;
354 result.OpenDreamTopicPort = settings.OpenDreamTopicPort;
355 result.AllowWebClient = settings.AllowWebClient!.Value;
356
357 var firstIteration = true;
358 do
359 {
360 if (!firstIteration)
361 {
362 cancellationToken.ThrowIfCancellationRequested();
363 await Task.Yield();
364 }
365
366 firstIteration = false;
367 result.Status = dd.Status;
368 result.SessionId = dd.SessionId;
369 result.LaunchTime = dd.LaunchTime;
370 result.ClientCount = dd.ClientCount;
371 }
372 while (result.Status == WatchdogStatus.Online && !result.SessionId.HasValue); // this is the one invalid combo, it's not that racy
373
374 result.SecurityLevel = settings.SecurityLevel!.Value;
375 result.Visibility = settings.Visibility!.Value;
378 result.ImmediateMemoryUsage = dd.MemoryUsage;
379
380 if (rstate == RebootState.Normal && knownForcedReboot)
381 result.SoftRestart = true;
382
383 result.StartupTimeout = settings.StartupTimeout!.Value;
384 result.HealthCheckSeconds = settings.HealthCheckSeconds!.Value;
385 result.DumpOnHealthCheckRestart = settings.DumpOnHealthCheckRestart!.Value;
386 result.TopicRequestTimeout = settings.TopicRequestTimeout!.Value;
387 result.AdditionalParameters = settings.AdditionalParameters;
388 result.StartProfiler = settings.StartProfiler;
389 result.LogOutput = settings.LogOutput;
390 result.MapThreads = settings.MapThreads;
391 result.Minidumps = settings.Minidumps;
392 }
393
394 if (revision)
395 {
396 var latestCompileJob = await instance.LatestCompileJob();
400 ?.ToApi();
401 if (latestCompileJob?.Id != result.ActiveCompileJob?.Id)
403 }
404
405 return Json(result);
406 });
407 }
408}
virtual ? long Id
The ID of the entity.
Definition EntityId.cs:13
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