mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 23:17:20 +01:00
Merge branch 'master' into linux-ordering
This commit is contained in:
@@ -3,6 +3,7 @@ pull_requests:
|
||||
do_not_increment_build_number: true
|
||||
environment:
|
||||
TGS4_TEST_DATABASE_TYPE: SqlServer
|
||||
TGS4_TEST_DUMP_API_SPEC: yes
|
||||
TGS4_TEST_CONNECTION_STRING: Server=(local)\SQL2017;Initial Catalog=TGS_Test;User ID=sa;Password=Password12!
|
||||
TGS4_TEST_GITHUB_TOKEN:
|
||||
secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK
|
||||
@@ -35,6 +36,7 @@ services:
|
||||
- mssql2017
|
||||
install:
|
||||
- choco install doxygen.install codecov graphviz.portable opencover.portable
|
||||
- npm i -g ibm-openapi-validator
|
||||
- nuget restore tgstation-server.sln
|
||||
- ps: Install-Product node 10
|
||||
build:
|
||||
@@ -65,6 +67,7 @@ test_script:
|
||||
- OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations..*" -output:".\server_coverage.xml" -oldstyle
|
||||
- ps: $wc = New-Object 'System.Net.WebClient'
|
||||
- ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Tests\TestResults\results.trx))
|
||||
- lint-openapi -c build/OpenApiValidationSettings.json C:/swagger.json
|
||||
after_test:
|
||||
- ps: build/UploadCoverage.ps1
|
||||
- ps: build/BuildDox.ps1
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ if($publish_dox){
|
||||
echo '# THIS BRANCH IS AUTO GENERATED BY APPVEYOR CI' > README.md
|
||||
|
||||
# Add in the swagger specification
|
||||
mv C:/swagger.json "$doxdir/swagger.json"
|
||||
cp C:/swagger.json "$doxdir/swagger.json"
|
||||
|
||||
# Need to create a .nojekyll file to allow filenames starting with an underscore
|
||||
# to be seen on the gh-pages site. Therefore creating an empty .nojekyll file.
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"shared": {
|
||||
"operations": {
|
||||
"no_operation_id": "error",
|
||||
"operation_id_case_convention": "off",
|
||||
"no_summary": "error",
|
||||
"no_array_responses": "off",
|
||||
"parameter_order": "error",
|
||||
"unused_tag": "error"
|
||||
},
|
||||
"pagination": {
|
||||
"pagination_style": "error"
|
||||
},
|
||||
"parameters": {
|
||||
"no_parameter_description": "error",
|
||||
"param_name_case_convention": "off",
|
||||
"invalid_type_format_pair": "off",
|
||||
"content_type_parameter": "error",
|
||||
"accept_type_parameter": "error",
|
||||
"authorization_parameter": "error",
|
||||
"required_param_has_default": "error"
|
||||
},
|
||||
"paths": {
|
||||
"missing_path_parameter": "error",
|
||||
"duplicate_path_parameter": "error",
|
||||
"snake_case_only": "off",
|
||||
"paths_case_convention": "off"
|
||||
},
|
||||
"responses": {
|
||||
"inline_response_schema": "off"
|
||||
},
|
||||
"security_definitions": {
|
||||
"unused_security_schemes": "error",
|
||||
"unused_security_scopes": "error"
|
||||
},
|
||||
"security": {
|
||||
"invalid_non_empty_security_array": "error"
|
||||
},
|
||||
"schemas": {
|
||||
"invalid_type_format_pair": "off",
|
||||
"snake_case_only": "off",
|
||||
"no_schema_description": "off",
|
||||
"no_property_description": "off",
|
||||
"description_mentions_json": "error",
|
||||
"array_of_arrays": "error",
|
||||
"property_case_convention": "off",
|
||||
"enum_case_convention": "error"
|
||||
},
|
||||
"walker": {
|
||||
"no_empty_descriptions": "error",
|
||||
"has_circular_references": "off",
|
||||
"$ref_siblings": "error",
|
||||
"duplicate_sibling_description": "error",
|
||||
"incorrect_ref_pattern": "error"
|
||||
}
|
||||
},
|
||||
"swagger2": {
|
||||
"operations": {
|
||||
"no_consumes_for_put_or_post": "error",
|
||||
"get_op_has_consumes": "error",
|
||||
"no_produces": "error"
|
||||
}
|
||||
},
|
||||
"oas3": {
|
||||
"operations": {
|
||||
"no_request_body_content": "error",
|
||||
"no_request_body_name": "off"
|
||||
},
|
||||
"parameters": {
|
||||
"no_in_property": "error",
|
||||
"invalid_in_property": "error",
|
||||
"missing_schema_or_content": "error",
|
||||
"has_schema_and_content": "error"
|
||||
},
|
||||
"responses": {
|
||||
"no_response_codes": "error",
|
||||
"no_success_response_codes": "error"
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-5
@@ -38,10 +38,9 @@ This document will reference the canonical C# models in the @ref Tgstation.Serve
|
||||
|
||||
@section api_header Headers
|
||||
|
||||
TGS4 expects this set of headers. Failure to provide them may result in 400 error responses
|
||||
TGS4 expects this set of headers. Failure to provide them will result in 400 error responses
|
||||
|
||||
- User-Agent: The user agent product header value of the calling program
|
||||
- Accept: application/json
|
||||
- Api: Another product header value representing the version of the API to use. Currently this must be: Tgstation.Server.Api/4.0.0.0
|
||||
|
||||
For POST, PATCH, and PUT requests you must also include the content type. Currently only json is supported
|
||||
@@ -87,8 +86,7 @@ POST "/" => @ref Tgstation.Server.Api.Models.Token
|
||||
|
||||
Headers:
|
||||
|
||||
- Username: `<your username>`
|
||||
- Authorization:Password `<your password>`
|
||||
- Authorization:basic `<Base64 encoded credentials in the form username:password>`
|
||||
|
||||
If the provided credentials are valid and your user account is enabled you will recieve a @ref Tgstation.Server.Api.Models.Token object
|
||||
@code{.json}
|
||||
@@ -101,7 +99,7 @@ If your account is disabled, you will recieve a 403 response.
|
||||
|
||||
You may recognize the bearer value as a <a href="https://jwt.io">Json Web Token</a>. This is a secure representation of your identity to the server. It expires after a set period of time or until your password changes. It must be present for requests made to all other APIs. To do so add the following header to your other requests
|
||||
|
||||
- Authorization:Bearer `<your bearer token>`
|
||||
- Authorization:bearer `<your bearer token>`
|
||||
|
||||
Continue to use this token until you begin to recieve 401 responses from the API. Then repeat the process to get a new one if your credentials are still valid
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ This is a second process spawned by the Host Watchdog which facilitates the vast
|
||||
|
||||
The server's entrypoint is in the @ref Tgstation.Server.Host.Program class. This class mainly determines if the Host watchdog is present and creates and runs the @ref Tgstation.Server.Host.Server class. That class then builds an ASP.NET Core web host using the @ref Tgstation.Server.Host.Core.Application class.
|
||||
|
||||
The @ref Tgstation.Server.Host.Core.Application class has two methods called by the framework. First the @ref Tgstation.Server.Host.Core.Application.ConfigureServices method sets up dependency injection of interfaces for Controllers, the @ref Tgstation.Server.Host.Models.DatabaseContext, and the component factories of the server. The framework handles constructing these things once the application starts. Configuration is loaded from the appropriate appSettings.json into the @ref Tgstation.Server.Host.Configuration classes for injection as well. Then @ref Tgstation.Server.Host.Core.Application.Configure method is run which sets up the web request pipeline which currently has the following stack of handlers:
|
||||
The @ref Tgstation.Server.Host.Core.Application class has two methods called by the framework. First the @ref Tgstation.Server.Host.Core.Application.ConfigureServices method sets up dependency injection of interfaces for Controllers, the @ref Tgstation.Server.Host.Database.DatabaseContext, and the component factories of the server. The framework handles constructing these things once the application starts. Configuration is loaded from the appropriate appSettings.json into the @ref Tgstation.Server.Host.Configuration classes for injection as well. Then @ref Tgstation.Server.Host.Core.Application.Configure method is run which sets up the web request pipeline which currently has the following stack of handlers:
|
||||
|
||||
- Catch any exceptions and respond with 500 and detailed HTML error page
|
||||
- Respond with 503 if the application is still starting or shutting down
|
||||
@@ -38,11 +38,11 @@ The @ref Tgstation.Server.Host.Core.Application class has two methods called by
|
||||
|
||||
Once the web host starts, the @ref Tgstation.Server.Host.Components.InstanceManager.StartAsync function is called (due to being registered as a IHostedService in @ref Tgstation.Server.Host.Core.Application) this is the only StartAsync implementation that should be called by the framework, others should be called from this to maintain a cohesive initialization order.
|
||||
|
||||
The first thing this function does is call @ref Tgstation.Server.Host.Models.DatabaseContext.Initialize which ensures the database is migrated, seeded, and ready to go. Then the @ref Tgstation.Server.Host.Core.JobManager is started, which cleans up any jobs that are considered "still running" in the database. Finally all instances configured to be online are created in parallel (See @ref arch_instance for onlining process) and the @ref Tgstation.Server.Host.Core.Application is signalled to stop blocking requests with 503 responses before they are processed.
|
||||
The first thing this function does is call @ref Tgstation.Server.Host.Database.DatabaseContext.Initialize which ensures the database is migrated, seeded, and ready to go. Then the @ref Tgstation.Server.Host.Core.JobManager is started, which cleans up any jobs that are considered "still running" in the database. Finally all instances configured to be online are created in parallel (See @ref arch_instance for onlining process) and the @ref Tgstation.Server.Host.Core.Application is signalled to stop blocking requests with 503 responses before they are processed.
|
||||
|
||||
@section arch_db Database and Context
|
||||
|
||||
The database is exposed as a series of DbSet<T> objects through @ref Tgstation.Server.Host.Models.IDatabaseContext . Queries are performed via async LINQ expressions. Inserts, updates, and deletes are done via modifiying the DbSet<T>s and then calling @ref Tgstation.Server.Host.Models.IDatabaseContext.Save . Do some reading on Entity Framework Core for a deeper understanding.
|
||||
The database is exposed as a series of DbSet<T> objects through @ref Tgstation.Server.Host.Database.IDatabaseContext . Queries are performed via async LINQ expressions. Inserts, updates, and deletes are done via modifiying the DbSet<T>s and then calling @ref Tgstation.Server.Host.Database.IDatabaseContext.Save . Do some reading on Entity Framework Core for a deeper understanding.
|
||||
|
||||
@section arch_controllers Controllers
|
||||
|
||||
@@ -98,9 +98,9 @@ The compilation process is a distinct series of steps:
|
||||
14. Symlink all `GameStaticFiles` to both the A and B directories
|
||||
15. Commit the @ref Tgstation.Server.Host.Models.CompileJob to the database
|
||||
|
||||
If any of the above steps fail, the target directory is deleted and the deployment is considered a bust. If all went well, after the @ref Tgstation.Server.Host.Models.Job completes the new CompileJob is loaded into the instance's @ref Tgstation.Server.Host.Components.Compiler.IDmbFactory .
|
||||
If any of the above steps fail, the target directory is deleted and the deployment is considered a bust. If all went well, after the @ref Tgstation.Server.Host.Models.Job completes the new CompileJob is loaded into the instance's @ref Tgstation.Server.Host.Components.Deployment.IDmbFactory .
|
||||
|
||||
The DmbFactory is where the @ref arch_watchdog gets the @ref Tgstation.Server.Host.Components.Compiler.IDmbProvider instances to run. Each CompileJob loaded into it is given a lock count. The latest CompileJob holds 1 lock and every DreamDaemon instance running that CompileJob holds another. Loading a new CompileJob releases the initial lock, and when all other locks are released the CompileJob's directory is deleted. Any directories in the `Game` folder not in use are also deleted when the Instance starts.
|
||||
The DmbFactory is where the @ref arch_watchdog gets the @ref Tgstation.Server.Host.Components.Deployment.IDmbProvider instances to run. Each CompileJob loaded into it is given a lock count. The latest CompileJob holds 1 lock and every DreamDaemon instance running that CompileJob holds another. Loading a new CompileJob releases the initial lock, and when all other locks are released the CompileJob's directory is deleted. Any directories in the `Game` folder not in use are also deleted when the Instance starts.
|
||||
|
||||
@section arch_chat Chat Bot System
|
||||
|
||||
@@ -128,7 +128,7 @@ That's a high level view of things, now let's get to the nitty gritty.
|
||||
|
||||
@subsection arch_wd_launch Launch
|
||||
|
||||
First the most recent @ref Tgstation.Server.Host.Components.Compiler.IDmbProvider is retrieved from the @ref Tgstation.Server.Host.Components.Compiler.IDmbFactory twice, adding 2 locks.
|
||||
First the most recent @ref Tgstation.Server.Host.Components.Deployment.IDmbProvider is retrieved from the @ref Tgstation.Server.Host.Components.Deployment.IDmbFactory twice, adding 2 locks.
|
||||
|
||||
This is used to launch a @ref Tgstation.Server.Host.Components.Watchdog.ISessionController via the watchdog's @ref Tgstation.Server.Host.Components.Watchdog.ISessionControllerFactory in the `A` directory of dmb providers @ref Tgstation.Server.Host.Models.CompileJob . This will be designated the `Alpha` server.
|
||||
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@
|
||||
#define TGS_EVENT_PORT_SWAP -2 //before a port change is about to happen, extra parameter is new port
|
||||
#define TGS_EVENT_REBOOT_MODE_CHANGE -1 //before a reboot mode change, extras parameters are the current and new reboot mode enums
|
||||
|
||||
//See the descriptions for these codes here: https://github.com/tgstation/tgstation-server/blob/master/src/Tgstation.Server.Host/Components/EventType.cs
|
||||
//See the descriptions for the parameters of these codes here: https://github.com/tgstation/tgstation-server/blob/master/src/Tgstation.Server.Host/Components/EventType.cs
|
||||
#define TGS_EVENT_REPO_RESET_ORIGIN 0
|
||||
#define TGS_EVENT_REPO_CHECKOUT 1
|
||||
#define TGS_EVENT_REPO_FETCH 2
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
Directory.Delete(assemblyStoragePath, true);
|
||||
Directory.CreateDirectory(defaultAssemblyPath);
|
||||
|
||||
var sourcePath = "../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.1";
|
||||
var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/netcoreapp2.1";
|
||||
foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
|
||||
Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath));
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Byond
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
public interface IByondExecutableLock : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="System.Version"/> of the locked executables
|
||||
/// The <see cref="global::System.Version"/> of the locked executables
|
||||
/// </summary>
|
||||
Version Version { get; }
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Byond
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
get => active;
|
||||
set
|
||||
{
|
||||
active = true;
|
||||
active = value;
|
||||
logger.LogDebug("Tracking {0}activated", !active ? "de" : String.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -5,11 +5,11 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Compiler
|
||||
namespace Tgstation.Server.Host.Components.Deployment
|
||||
{
|
||||
/// <summary>
|
||||
/// Standard <see cref="IDmbFactory"/>
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Compiler
|
||||
namespace Tgstation.Server.Host.Components.Deployment
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DmbProvider : IDmbProvider
|
||||
+39
-42
@@ -12,10 +12,11 @@ using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Compiler
|
||||
namespace Tgstation.Server.Host.Components.Deployment
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DreamMaker : IDreamMaker
|
||||
@@ -60,16 +61,6 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// </summary>
|
||||
readonly ISessionControllerFactory sessionControllerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ICompileJobConsumer"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly ICompileJobConsumer compileJobConsumer;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IApplication"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly IApplication application;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IEventConsumer"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
@@ -100,6 +91,40 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// </summary>
|
||||
bool compiling;
|
||||
|
||||
/// <summary>
|
||||
/// Construct <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
/// <param name="byond">The value of <see cref="byond"/></param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
/// <param name="configuration">The value of <see cref="configuration"/></param>
|
||||
/// <param name="sessionControllerFactory">The value of <see cref="sessionControllerFactory"/></param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
/// <param name="chat">The value of <see cref="chat"/></param>
|
||||
/// <param name="processExecutor">The value of <see cref="processExecutor"/></param>
|
||||
/// <param name="watchdog">The value of <see cref="watchdog"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
public DreamMaker(
|
||||
IByondManager byond,
|
||||
IIOManager ioManager,
|
||||
StaticFiles.IConfiguration configuration,
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IEventConsumer eventConsumer,
|
||||
IChat chat,
|
||||
IProcessExecutor processExecutor,
|
||||
IWatchdog watchdog,
|
||||
ILogger<DreamMaker> logger)
|
||||
{
|
||||
this.byond = byond ?? throw new ArgumentNullException(nameof(byond));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory));
|
||||
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
|
||||
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
|
||||
this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gradually triggers a given <paramref name="progressReporter"/> over a given <paramref name="estimatedDuration"/>
|
||||
/// </summary>
|
||||
@@ -107,11 +132,12 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
/// <param name="estimatedDuration">A <see cref="TimeSpan"/> representing the duration to give progress over</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
static async Task ProgressTask(Action<int> progressReporter, TimeSpan estimatedDuration, CancellationToken cancellationToken)
|
||||
async Task ProgressTask(Action<int> progressReporter, TimeSpan estimatedDuration, CancellationToken cancellationToken)
|
||||
{
|
||||
progressReporter(0);
|
||||
var sleepInterval = estimatedDuration / 100;
|
||||
|
||||
logger.LogDebug("Compile is expected to take: {0}", estimatedDuration);
|
||||
try
|
||||
{
|
||||
for (var I = 0; I < 99; ++I)
|
||||
@@ -123,35 +149,6 @@ namespace Tgstation.Server.Host.Components.Compiler
|
||||
catch (OperationCanceledException) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
/// <param name="byond">The value of <see cref="byond"/></param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
|
||||
/// <param name="configuration">The value of <see cref="configuration"/></param>
|
||||
/// <param name="sessionControllerFactory">The value of <see cref="sessionControllerFactory"/></param>
|
||||
/// <param name="compileJobConsumer">The value of <see cref="compileJobConsumer"/></param>
|
||||
/// <param name="application">The value of <see cref="application"/></param>
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
/// <param name="chat">The value of <see cref="chat"/></param>
|
||||
/// <param name="processExecutor">The value of <see cref="processExecutor"/></param>
|
||||
/// <param name="watchdog">The value of <see cref="watchdog"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
public DreamMaker(IByondManager byond, IIOManager ioManager, StaticFiles.IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, IChat chat, IProcessExecutor processExecutor, IWatchdog watchdog, ILogger<DreamMaker> logger)
|
||||
{
|
||||
this.byond = byond ?? throw new ArgumentNullException(nameof(byond));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
this.sessionControllerFactory = sessionControllerFactory ?? throw new ArgumentNullException(nameof(sessionControllerFactory));
|
||||
this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer));
|
||||
this.application = application ?? throw new ArgumentNullException(nameof(application));
|
||||
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
|
||||
this.chat = chat ?? throw new ArgumentNullException(nameof(chat));
|
||||
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
|
||||
this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run a quick DD instance to test the DMAPI is installed on the target code
|
||||
/// </summary>
|
||||
+1
-1
@@ -4,7 +4,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Compiler
|
||||
namespace Tgstation.Server.Host.Components.Deployment
|
||||
{
|
||||
/// <summary>
|
||||
/// Sink for <see cref="CompileJob"/>s
|
||||
+1
-1
@@ -3,7 +3,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Compiler
|
||||
namespace Tgstation.Server.Host.Components.Deployment
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for <see cref="IDmbProvider"/>s
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
namespace Tgstation.Server.Host.Components.Deployment
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides absolute paths to the latest compiled .dmbs
|
||||
+1
-1
@@ -3,7 +3,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Compiler
|
||||
namespace Tgstation.Server.Host.Components.Deployment
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing the compiler
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Compiler
|
||||
namespace Tgstation.Server.Host.Components.Deployment
|
||||
{
|
||||
/// <summary>
|
||||
/// Temporary <see cref="IDmbProvider"/>
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Deployment
|
||||
{
|
||||
/// <summary>
|
||||
/// A windows <see cref="IDmbProvider"/> that uses symlinks.
|
||||
/// </summary>
|
||||
sealed class WindowsSwappableDmbProvider : IDmbProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// The directory where the <see cref="baseProvider"/> is symlinked to.
|
||||
/// </summary>
|
||||
const string LiveGameDirectory = "Live";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DmbName => baseProvider.DmbName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string PrimaryDirectory => ioManager.ResolvePath(LiveGameDirectory);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string SecondaryDirectory => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public CompileJob CompileJob => baseProvider.CompileJob;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDmbProvider"/> we are swapping for.
|
||||
/// </summary>
|
||||
readonly IDmbProvider baseProvider;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> to use.
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISymlinkFactory"/> to use.
|
||||
/// </summary>
|
||||
readonly ISymlinkFactory symlinkFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowsSwappableDmbProvider"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="baseProvider">The value of <see cref="baseProvider"/>.</param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
|
||||
/// <param name="symlinkFactory">The value of <see cref="symlinkFactory"/>.</param>
|
||||
public WindowsSwappableDmbProvider(IDmbProvider baseProvider, IIOManager ioManager, ISymlinkFactory symlinkFactory)
|
||||
{
|
||||
this.baseProvider = baseProvider ?? throw new ArgumentNullException(nameof(baseProvider));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => baseProvider.Dispose();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void KeepAlive() => baseProvider.KeepAlive();
|
||||
|
||||
/// <summary>
|
||||
/// Make the <see cref="WindowsSwappableDmbProvider"/> active by replacing the live link with our <see cref="CompileJob"/>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
public async Task MakeActive(CancellationToken cancellationToken)
|
||||
{
|
||||
// Note this comment from TGS3:
|
||||
// These next two lines should be atomic but this is the best we can do
|
||||
await ioManager.DeleteDirectory(LiveGameDirectory, cancellationToken).ConfigureAwait(false);
|
||||
await symlinkFactory.CreateSymbolicLink(baseProvider.PrimaryDirectory, ioManager.ResolvePath(LiveGameDirectory), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Components.StaticFiles;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
@@ -61,7 +62,7 @@ namespace Tgstation.Server.Host.Components
|
||||
Task SetAutoUpdateInterval(uint newInterval);
|
||||
|
||||
/// <summary>
|
||||
/// Run the compile job and insert it into the database. Meant to be called by a <see cref="Core.IJobManager"/>
|
||||
/// Run the compile job and insert it into the database. Meant to be called by a <see cref="Jobs.IJobManager"/>
|
||||
/// </summary>
|
||||
/// <param name="job">The running <see cref="Job"/></param>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the operation</param>
|
||||
|
||||
@@ -10,10 +10,12 @@ using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Compiler;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
@@ -109,7 +111,22 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByondManager byondManager, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, StaticFiles.IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory, IJobManager jobManager, IEventConsumer eventConsumer, IGitHubClientFactory gitHubClientFactory, ILogger<Instance> logger)
|
||||
public Instance(
|
||||
Api.Models.Instance metadata,
|
||||
IRepositoryManager repositoryManager,
|
||||
IByondManager byondManager,
|
||||
IDreamMaker dreamMaker,
|
||||
IWatchdog watchdog,
|
||||
IChat chat,
|
||||
StaticFiles.IConfiguration
|
||||
configuration,
|
||||
ICompileJobConsumer compileJobConsumer,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IDmbFactory dmbFactory,
|
||||
IJobManager jobManager,
|
||||
IEventConsumer eventConsumer,
|
||||
IGitHubClientFactory gitHubClientFactory,
|
||||
ILogger<Instance> logger)
|
||||
{
|
||||
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
|
||||
RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
|
||||
@@ -324,7 +341,7 @@ namespace Tgstation.Server.Host.Components
|
||||
try
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromMinutes(minutes > Int32.MaxValue ? Int32.MaxValue : (int)minutes), cancellationToken).ConfigureAwait(false);
|
||||
logger.LogDebug("Beginning auto update...");
|
||||
logger.LogInformation("Beginning auto update...");
|
||||
await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, new List<string>(), cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
|
||||
@@ -6,12 +6,15 @@ using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Chat.Commands;
|
||||
using Tgstation.Server.Host.Components.Compiler;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
@@ -129,7 +132,25 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="networkPromptReaper">The value of <see cref="networkPromptReaper"/></param>
|
||||
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/></param>
|
||||
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/></param>
|
||||
public InstanceFactory(IIOManager ioManager, IDatabaseContextFactory databaseContextFactory, IApplication application, ILoggerFactory loggerFactory, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IByondInstaller byondInstaller, IChatFactory chatFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IWatchdogFactory watchdogFactory, IJobManager jobManager, ICredentialsProvider credentialsProvider, INetworkPromptReaper networkPromptReaper, IGitHubClientFactory gitHubClientFactory, IPlatformIdentifier platformIdentifier)
|
||||
public InstanceFactory(
|
||||
IIOManager ioManager,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IApplication application,
|
||||
ILoggerFactory loggerFactory,
|
||||
IByondTopicSender byondTopicSender,
|
||||
ICryptographySuite cryptographySuite,
|
||||
ISynchronousIOManager synchronousIOManager,
|
||||
ISymlinkFactory symlinkFactory,
|
||||
IByondInstaller byondInstaller,
|
||||
IChatFactory chatFactory,
|
||||
IProcessExecutor processExecutor,
|
||||
IPostWriteHandler postWriteHandler,
|
||||
IWatchdogFactory watchdogFactory,
|
||||
IJobManager jobManager,
|
||||
ICredentialsProvider credentialsProvider,
|
||||
INetworkPromptReaper networkPromptReaper,
|
||||
IGitHubClientFactory gitHubClientFactory,
|
||||
IPlatformIdentifier platformIdentifier)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
@@ -152,7 +173,7 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CA1506 // TODO: Decomplexify
|
||||
#pragma warning disable CA1506 // TODO: Decomplexify
|
||||
public IInstance CreateInstance(Models.Instance metadata)
|
||||
{
|
||||
// Create the ioManager for the instance
|
||||
@@ -166,28 +187,36 @@ namespace Tgstation.Server.Host.Components
|
||||
|
||||
var configuration = new StaticFiles.Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, processExecutor, postWriteHandler, platformIdentifier, loggerFactory.CreateLogger<StaticFiles.Configuration>());
|
||||
var eventConsumer = new EventConsumer(configuration);
|
||||
|
||||
var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, loggerFactory.CreateLogger<DmbFactory>(), metadata.CloneMetadata());
|
||||
var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer, credentialsProvider, loggerFactory.CreateLogger<Repository.Repository>(), loggerFactory.CreateLogger<RepositoryManager>());
|
||||
try
|
||||
{
|
||||
var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager, eventConsumer, credentialsProvider, loggerFactory.CreateLogger<Repository.Repository>(), loggerFactory.CreateLogger<RepositoryManager>());
|
||||
var byond = new ByondManager(byondIOManager, byondInstaller, eventConsumer, loggerFactory.CreateLogger<ByondManager>());
|
||||
|
||||
var commandFactory = new CommandFactory(application, byond, repoManager, databaseContextFactory, metadata);
|
||||
|
||||
var chat = chatFactory.CreateChat(instanceIoManager, commandFactory, metadata.ChatSettings);
|
||||
try
|
||||
{
|
||||
var byond = new ByondManager(byondIOManager, byondInstaller, eventConsumer, loggerFactory.CreateLogger<ByondManager>());
|
||||
var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, networkPromptReaper, platformIdentifier, loggerFactory, metadata.CloneMetadata());
|
||||
|
||||
var commandFactory = new CommandFactory(application, byond, repoManager, databaseContextFactory, metadata);
|
||||
|
||||
var chat = chatFactory.CreateChat(instanceIoManager, commandFactory, metadata.ChatSettings);
|
||||
var dmbFactory = new DmbFactory(databaseContextFactory, gameIoManager, loggerFactory.CreateLogger<DmbFactory>(), metadata.CloneMetadata());
|
||||
try
|
||||
{
|
||||
var sessionControllerFactory = new SessionControllerFactory(processExecutor, byond, byondTopicSender, cryptographySuite, application, gameIoManager, chat, networkPromptReaper, platformIdentifier, loggerFactory, metadata.CloneMetadata());
|
||||
var reattachInfoHandler = new ReattachInfoHandler(databaseContextFactory, dmbFactory, loggerFactory.CreateLogger<ReattachInfoHandler>(), metadata.CloneMetadata());
|
||||
var watchdog = watchdogFactory.CreateWatchdog(chat, dmbFactory, reattachInfoHandler, configuration, sessionControllerFactory, metadata.CloneMetadata(), metadata.DreamDaemonSettings);
|
||||
var watchdog = watchdogFactory.CreateWatchdog(
|
||||
chat,
|
||||
dmbFactory,
|
||||
reattachInfoHandler,
|
||||
configuration,
|
||||
sessionControllerFactory,
|
||||
gameIoManager,
|
||||
metadata.CloneMetadata(),
|
||||
metadata.DreamDaemonSettings);
|
||||
eventConsumer.SetWatchdog(watchdog);
|
||||
commandFactory.SetWatchdog(watchdog);
|
||||
try
|
||||
{
|
||||
var dreamMaker = new DreamMaker(byond, gameIoManager, configuration, sessionControllerFactory, dmbFactory, application, eventConsumer, chat, processExecutor, watchdog, loggerFactory.CreateLogger<DreamMaker>());
|
||||
var dreamMaker = new DreamMaker(byond, gameIoManager, configuration, sessionControllerFactory, eventConsumer, chat, processExecutor, watchdog, loggerFactory.CreateLogger<DreamMaker>());
|
||||
|
||||
return new Instance(metadata.CloneMetadata(), repoManager, byond, dreamMaker, watchdog, chat, configuration, dmbFactory, databaseContextFactory, dmbFactory, jobManager, eventConsumer, gitHubClientFactory, loggerFactory.CreateLogger<Instance>());
|
||||
}
|
||||
@@ -199,19 +228,19 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
catch
|
||||
{
|
||||
chat.Dispose();
|
||||
dmbFactory.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
repoManager.Dispose();
|
||||
chat.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
dmbFactory.Dispose();
|
||||
repoManager.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace Tgstation.Server.Host.Components.Interop
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogDebug("Exception while trying to handle command json write: {0}", ex);
|
||||
logger.LogError("Exception while trying to handle command json write: {0}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components.Compiler;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Z.EntityFramework.Plus;
|
||||
|
||||
namespace Tgstation.Server.Host.Components
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using LibGit2Sharp.Handlers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
|
||||
@@ -8,8 +8,8 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
@@ -84,6 +84,11 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// </summary>
|
||||
readonly Action onDispose;
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="Repository"/> was disposed.
|
||||
/// </summary>
|
||||
bool disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a given <paramref name="progressReporter"/> to a <see cref="LibGit2Sharp.Handlers.CheckoutProgressHandler"/>
|
||||
/// </summary>
|
||||
@@ -120,9 +125,17 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
lock (onDispose)
|
||||
{
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
logger.LogTrace("Disposing...");
|
||||
repository.Dispose();
|
||||
onDispose.Invoke();
|
||||
onDispose();
|
||||
}
|
||||
|
||||
void GetRepositoryOwnerName(string remote, out string owner, out string name)
|
||||
|
||||
@@ -12,6 +12,7 @@ using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
{
|
||||
@@ -87,7 +88,14 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/></param>
|
||||
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
public Configuration(IIOManager ioManager, ISynchronousIOManager synchronousIOManager, ISymlinkFactory symlinkFactory, IProcessExecutor processExecutor, IPostWriteHandler postWriteHandler, IPlatformIdentifier platformIdentifier, ILogger<Configuration> logger)
|
||||
public Configuration(
|
||||
IIOManager ioManager,
|
||||
ISynchronousIOManager synchronousIOManager,
|
||||
ISymlinkFactory symlinkFactory,
|
||||
IProcessExecutor processExecutor,
|
||||
IPostWriteHandler postWriteHandler,
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
ILogger<Configuration> logger)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager));
|
||||
|
||||
@@ -1,37 +1,40 @@
|
||||
using Byond.TopicSender;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Compiler;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IWatchdog"/> that manages one server.
|
||||
/// </summary>
|
||||
sealed class BasicWatchdog : WatchdogBase
|
||||
class BasicWatchdog : WatchdogBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool AlphaIsActive => true;
|
||||
public sealed override bool AlphaIsActive => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Models.CompileJob ActiveCompileJob => server?.Dmb.CompileJob;
|
||||
public sealed override Models.CompileJob ActiveCompileJob => Server?.Dmb.CompileJob;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override RebootState? RebootState => server?.RebootState;
|
||||
public sealed override RebootState? RebootState => Server?.RebootState;
|
||||
|
||||
/// <summary>
|
||||
/// The single <see cref="ISessionController"/>.
|
||||
/// </summary>
|
||||
ISessionController server;
|
||||
protected ISessionController Server { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExperimentalWatchdog"/> <see langword="class"/>.
|
||||
/// Initializes a new instance of the <see cref="BasicWatchdog"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="chat">The <see cref="IChat"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
@@ -84,8 +87,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
switch (reason)
|
||||
{
|
||||
case MonitorActivationReason.ActiveServerCrashed:
|
||||
string exitWord = server.TerminationWasRequested ? "exited" : "crashed";
|
||||
if (server.RebootState == Watchdog.RebootState.Shutdown)
|
||||
string exitWord = Server.TerminationWasRequested ? "exited" : "crashed";
|
||||
if (Server.RebootState == Watchdog.RebootState.Shutdown)
|
||||
{
|
||||
// the time for graceful shutdown is now
|
||||
await Chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Server {0}! Exiting due to graceful termination request...", exitWord), cancellationToken).ConfigureAwait(false);
|
||||
@@ -96,14 +99,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
await Chat.SendWatchdogMessage(String.Format(CultureInfo.InvariantCulture, "Server {0}! Rebooting...", exitWord), cancellationToken).ConfigureAwait(false);
|
||||
return MonitorAction.Restart;
|
||||
case MonitorActivationReason.ActiveServerRebooted:
|
||||
var rebootState = server.RebootState;
|
||||
server.ResetRebootState();
|
||||
var rebootState = Server.RebootState;
|
||||
Server.ResetRebootState();
|
||||
|
||||
switch (rebootState)
|
||||
{
|
||||
case Watchdog.RebootState.Normal:
|
||||
bool dmbUpdatePending = ActiveLaunchParameters != LastLaunchParameters;
|
||||
return dmbUpdatePending ? MonitorAction.Restart : MonitorAction.Continue;
|
||||
return HandleNormalReboot();
|
||||
case Watchdog.RebootState.Restart:
|
||||
return MonitorAction.Restart;
|
||||
case Watchdog.RebootState.Shutdown:
|
||||
@@ -116,8 +118,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
|
||||
case MonitorActivationReason.ActiveLaunchParametersUpdated:
|
||||
await Server.SetRebootState(Watchdog.RebootState.Restart, cancellationToken).ConfigureAwait(false);
|
||||
return MonitorAction.Continue;
|
||||
case MonitorActivationReason.NewDmbAvailable:
|
||||
await server.SetRebootState(Watchdog.RebootState.Restart, cancellationToken).ConfigureAwait(false);
|
||||
await HandleNewDmbAvailable(cancellationToken).ConfigureAwait(false);
|
||||
return MonitorAction.Continue;
|
||||
case MonitorActivationReason.InactiveServerCrashed:
|
||||
case MonitorActivationReason.InactiveServerRebooted:
|
||||
@@ -129,31 +133,28 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override WatchdogReattachInformation CreateReattachInformation()
|
||||
protected sealed override WatchdogReattachInformation CreateReattachInformation()
|
||||
=> new WatchdogReattachInformation
|
||||
{
|
||||
AlphaIsActive = true,
|
||||
Alpha = server?.Release()
|
||||
Alpha = Server?.Release()
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DisposeAndNullControllers()
|
||||
{
|
||||
server?.Dispose();
|
||||
server = null;
|
||||
Server?.Dispose();
|
||||
Server = null;
|
||||
Running = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override ISessionController GetActiveController() => server;
|
||||
protected sealed override ISessionController GetActiveController() => Server;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task InitControllers(Action callBeforeRecurse, Task chatTask, WatchdogReattachInformation reattachInfo, CancellationToken cancellationToken)
|
||||
protected sealed override async Task InitControllers(Action callBeforeRecurse, Task chatTask, WatchdogReattachInformation reattachInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
// good ole sanity, should never fucking trigger but i don't trust myself even though I should
|
||||
// TODO: Unit test this instead?
|
||||
if (server != null)
|
||||
throw new InvalidOperationException("Entered LaunchNoLock with server not being null!");
|
||||
Debug.Assert(Server == null, "Entered LaunchNoLock with server not being null!");
|
||||
|
||||
// don't need a new dmb if reattaching
|
||||
var doesntNeedNewDmb = reattachInfo?.Alpha != null && reattachInfo?.Bravo != null;
|
||||
@@ -179,7 +180,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
// The tasks pertaining to server startup times are in the ISessionControllers
|
||||
Task<ISessionController> serverLaunchTask, inactiveReattachTask;
|
||||
if (!doesntNeedNewDmb)
|
||||
{
|
||||
dmbToUse = await PrepServerForLaunch(dmbToUse, cancellationToken).ConfigureAwait(false);
|
||||
serverLaunchTask = SessionControllerFactory.LaunchNew(ActiveLaunchParameters, dmbToUse, null, true, true, false, cancellationToken);
|
||||
}
|
||||
else
|
||||
serverLaunchTask = SessionControllerFactory.Reattach(serverToReattach, cancellationToken);
|
||||
|
||||
@@ -190,31 +194,31 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
inactiveReattachTask = Task.FromResult<ISessionController>(null);
|
||||
|
||||
// retrieve the session controller
|
||||
server = await serverLaunchTask.ConfigureAwait(false);
|
||||
Server = await serverLaunchTask.ConfigureAwait(false);
|
||||
|
||||
// failed reattaches will return null
|
||||
server?.SetHighPriority();
|
||||
Server?.SetHighPriority();
|
||||
|
||||
var inactiveServerController = await inactiveReattachTask.ConfigureAwait(false);
|
||||
inactiveServerController?.Dispose();
|
||||
inactiveServerWasKilled = inactiveServerController != null;
|
||||
|
||||
// possiblity of null servers due to failed reattaches
|
||||
if (server == null)
|
||||
if (Server == null)
|
||||
{
|
||||
callBeforeRecurse();
|
||||
await NotifyOfFailedReattach(thereIsAnInactiveServerToKill && !inactiveServerWasKilled, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await CheckLaunchResult(server, "Server", cancellationToken).ConfigureAwait(false);
|
||||
await CheckLaunchResult(Server, "Server", cancellationToken).ConfigureAwait(false);
|
||||
|
||||
server.EnableCustomChatCommands();
|
||||
Server.EnableCustomChatCommands();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// kill the controllers
|
||||
bool serverWasActive = server != null;
|
||||
bool serverWasActive = Server != null;
|
||||
DisposeAndNullControllers();
|
||||
|
||||
// server didn't get control of this dmb
|
||||
@@ -228,7 +232,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task MonitorLifetimes(CancellationToken cancellationToken)
|
||||
protected sealed override async Task MonitorLifetimes(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.LogTrace("Entered MonitorLifetimes");
|
||||
|
||||
@@ -243,11 +247,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
Logger.LogDebug("Iteration {0} of monitor loop", iteration);
|
||||
try
|
||||
{
|
||||
Logger.LogDebug("Server Compile Job ID: {0}", server.Dmb.CompileJob.Id);
|
||||
Logger.LogDebug("Server Compile Job ID: {0}", Server.Dmb.CompileJob.Id);
|
||||
|
||||
// load the activation tasks into local variables
|
||||
Task activeServerLifetime = server.Lifetime;
|
||||
var activeServerReboot = server.OnReboot;
|
||||
Task activeServerLifetime = Server.Lifetime;
|
||||
var activeServerReboot = Server.OnReboot;
|
||||
Task activeLaunchParametersChanged = ActiveParametersUpdated.Task;
|
||||
var newDmbAvailable = DmbFactory.OnNewerDmb;
|
||||
|
||||
@@ -291,7 +295,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|| CheckActivationReason(ref activeServerReboot, MonitorActivationReason.ActiveServerRebooted)
|
||||
|| CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable)
|
||||
|| CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated))
|
||||
{
|
||||
Logger.LogTrace("Monitor activation: {0}", activationReason);
|
||||
nextAction = await HandleMonitorWakeup(activationReason, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
moreActivationsToProcess = false;
|
||||
}
|
||||
@@ -355,5 +362,30 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
Logger.LogTrace("Monitor exiting...");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for <see cref="MonitorActivationReason.ActiveServerRebooted"/> when the <see cref="RebootState"/> is <see cref="RebootState.Normal"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="MonitorAction"/> to take.</returns>
|
||||
protected virtual MonitorAction HandleNormalReboot()
|
||||
{
|
||||
bool dmbUpdatePending = ActiveLaunchParameters != LastLaunchParameters;
|
||||
return dmbUpdatePending ? MonitorAction.Restart : MonitorAction.Continue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handler for <see cref="MonitorActivationReason.NewDmbAvailable"/>.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
protected virtual Task HandleNewDmbAvailable(CancellationToken cancellationToken) => Server.SetRebootState(Watchdog.RebootState.Restart, cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Prepare the server to launch a new instance with the <see cref="WatchdogBase.ActiveLaunchParameters"/> and a given <paramref name="dmbToUse"/>.
|
||||
/// </summary>
|
||||
/// <param name="dmbToUse">The <see cref="IDmbProvider"/> to be launched. Will not be disposed by this function.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the modified <see cref="IDmbProvider"/> to be used.</returns>
|
||||
protected virtual Task<IDmbProvider> PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken) => Task.FromResult(dmbToUse);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
@@ -96,5 +97,14 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> SetRebootState(RebootState newRebootState, CancellationToken cancellationToken) => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReplaceDmbProvider(IDmbProvider newProvider) => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Suspend() => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Resume() => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Compiler;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
@@ -438,7 +440,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|| CheckActivationReason(ref inactiveServerStartup, MonitorActivationReason.InactiveServerStartupComplete)
|
||||
|| CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable)
|
||||
|| CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated))
|
||||
{
|
||||
Logger.LogTrace("Monitor activation: {0}", activationReason);
|
||||
await HandlerMonitorWakeup(activationReason, monitorState, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
moreActivationsToProcess = false;
|
||||
}
|
||||
@@ -513,10 +518,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
#pragma warning disable CA1502 // TODO: Decomplexify
|
||||
protected override async Task InitControllers(Action callBeforeRecurse, Task chatTask, WatchdogReattachInformation reattachInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
// good ole sanity, should never fucking trigger but i don't trust myself even though I should
|
||||
// TODO: Unit test this instead?
|
||||
if (alphaServer != null || bravoServer != null)
|
||||
throw new InvalidOperationException("Entered LaunchNoLock with one or more of the servers not being null!");
|
||||
Debug.Assert(alphaServer == null && bravoServer == null, "Entered LaunchNoLock with one or more of the servers not being null!");
|
||||
|
||||
// don't need a new dmb if reattaching
|
||||
var doesntNeedNewDmb = reattachInfo?.Alpha != null && reattachInfo?.Bravo != null;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
@@ -25,7 +26,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
bool TerminationWasRequested { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The DMAPI <see cref="Components.Watchdog.ApiValidationStatus"/>
|
||||
/// The DMAPI <see cref="Watchdog.ApiValidationStatus"/>
|
||||
/// </summary>
|
||||
ApiValidationStatus ApiValidationStatus { get; }
|
||||
|
||||
@@ -55,7 +56,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
Task OnReboot { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Releases the <see cref="IProcess"/> without terminating it. Also calls <see cref="System.IDisposable.Dispose"/>
|
||||
/// Releases the <see cref="IProcess"/> without terminating it. Also calls <see cref="global::System.IDisposable.Dispose"/>
|
||||
/// </summary>
|
||||
/// <returns><see cref="ReattachInformation"/> which can be used to create a new <see cref="ISessionController"/> similar to this one</returns>
|
||||
ReattachInformation Release();
|
||||
@@ -85,7 +86,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
Task<bool> SetRebootState(RebootState newRebootState, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Changes <see cref="RebootState"/> to <see cref="Components.Watchdog.RebootState.Normal"/> without telling the DMAPI
|
||||
/// Changes <see cref="RebootState"/> to <see cref="Watchdog.RebootState.Normal"/> without telling the DMAPI
|
||||
/// </summary>
|
||||
void ResetRebootState();
|
||||
|
||||
@@ -93,5 +94,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// Enables the reading of custom chat commands from the <see cref="ISessionController"/>
|
||||
/// </summary>
|
||||
void EnableCustomChatCommands();
|
||||
|
||||
/// <summary>
|
||||
/// Replace <see cref="Dmb"/> with a given <paramref name="newProvider"/>, disposing the old one.
|
||||
/// </summary>
|
||||
/// <param name="newProvider">The new <see cref="IDmbProvider"/>.</param>
|
||||
void ReplaceDmbProvider(IDmbProvider newProvider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Compiler;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
@@ -17,9 +18,18 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="reattachInfoHandler">The <see cref="IReattachInfoHandler"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="eventConsumer">The <see cref="IEventConsumer"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="IWatchdog"/>.</param>
|
||||
/// <param name="instance">The <see cref="Instance"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="settings">The initial <see cref="DreamDaemonSettings"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <returns>A new <see cref="IWatchdog"/></returns>
|
||||
IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings);
|
||||
IWatchdog CreateWatchdog(
|
||||
IChat chat,
|
||||
IDmbFactory dmbFactory,
|
||||
IReattachInfoHandler reattachInfoHandler,
|
||||
IEventConsumer eventConsumer,
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IIOManager ioManager,
|
||||
Api.Models.Instance instance,
|
||||
DreamDaemonSettings settings);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
public sealed class LaunchResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The time it took for <see cref="System.Diagnostics.Process.WaitForInputIdle()"/> to return. If <see langword="null"/> the startup timed out
|
||||
/// The time it took for <see cref="global::System.Diagnostics.Process.WaitForInputIdle()"/> to return. If <see langword="null"/> the startup timed out
|
||||
/// </summary>
|
||||
public TimeSpan? StartupTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="System.Diagnostics.Process.ExitCode"/> if it exited
|
||||
/// The <see cref="global::System.Diagnostics.Process.ExitCode"/> if it exited
|
||||
/// </summary>
|
||||
public int? ExitCode { get; set; }
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
public bool InactiveServerCritFail { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The next <see cref="MonitorAction"/> to take in <see cref="ExperimentalWatchdog.MonitorLifetimes(System.Threading.CancellationToken)"/>
|
||||
/// The next <see cref="MonitorAction"/> to take in <see cref="ExperimentalWatchdog.MonitorLifetimes(global::System.Threading.CancellationToken)"/>
|
||||
/// </summary>
|
||||
public MonitorAction NextAction { get; set; }
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
@@ -11,8 +11,9 @@ using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Components.Interop;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
@@ -180,7 +181,17 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="launchSecurityLevel">The value of <see cref="launchSecurityLevel"/></param>
|
||||
/// <param name="startupTimeout">The optional time to wait before failing the <see cref="LaunchResult"/></param>
|
||||
public SessionController(ReattachInformation reattachInformation, IProcess process, IByondExecutableLock byondLock, IByondTopicSender byondTopicSender, IJsonTrackingContext chatJsonTrackingContext, ICommContext interopContext, IChat chat, ILogger<SessionController> logger, DreamDaemonSecurity? launchSecurityLevel, uint? startupTimeout)
|
||||
public SessionController(
|
||||
ReattachInformation reattachInformation,
|
||||
IProcess process,
|
||||
IByondExecutableLock byondLock,
|
||||
IByondTopicSender byondTopicSender,
|
||||
IJsonTrackingContext chatJsonTrackingContext,
|
||||
ICommContext interopContext,
|
||||
IChat chat,
|
||||
ILogger<SessionController> logger,
|
||||
DreamDaemonSecurity? launchSecurityLevel,
|
||||
uint? startupTimeout)
|
||||
{
|
||||
this.chatJsonTrackingContext = chatJsonTrackingContext; // null valid
|
||||
this.reattachInformation = reattachInformation ?? throw new ArgumentNullException(nameof(reattachInformation));
|
||||
@@ -447,6 +458,15 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
async Task<string> SendCommand(string command, ushort? overridePort, CancellationToken cancellationToken)
|
||||
{
|
||||
if (Lifetime.IsCompleted)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Attempted to send a command to an inactive SessionController{1}: {0}",
|
||||
command,
|
||||
overridePort.HasValue ? $" (Override port: {overridePort.Value})" : String.Empty);
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var commandString = String.Format(CultureInfo.InvariantCulture,
|
||||
@@ -528,5 +548,23 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetHighPriority() => process.SetHighPriority();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Suspend() => process.Suspend();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Resume() => process.Resume();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReplaceDmbProvider(IDmbProvider dmbProvider)
|
||||
{
|
||||
#pragma warning disable IDE0016 // Use 'throw' expression
|
||||
if (dmbProvider == null)
|
||||
throw new ArgumentNullException(nameof(dmbProvider));
|
||||
#pragma warning restore IDE0016 // Use 'throw' expression
|
||||
|
||||
reattachInformation.Dmb.Dispose();
|
||||
reattachInformation.Dmb = dmbProvider;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,12 @@ using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Components.Interop;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
@@ -111,7 +113,18 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="networkPromptReaper">The value of <see cref="networkPromptReaper"/></param>
|
||||
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/></param>
|
||||
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
|
||||
public SessionControllerFactory(IProcessExecutor processExecutor, IByondManager byond, IByondTopicSender byondTopicSender, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, INetworkPromptReaper networkPromptReaper, IPlatformIdentifier platformIdentifier, ILoggerFactory loggerFactory, Api.Models.Instance instance)
|
||||
public SessionControllerFactory(
|
||||
IProcessExecutor processExecutor,
|
||||
IByondManager byond,
|
||||
IByondTopicSender byondTopicSender,
|
||||
ICryptographySuite cryptographySuite,
|
||||
IApplication application,
|
||||
IIOManager ioManager,
|
||||
IChat chat,
|
||||
INetworkPromptReaper networkPromptReaper,
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
ILoggerFactory loggerFactory,
|
||||
Api.Models.Instance instance)
|
||||
{
|
||||
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
|
||||
this.byond = byond ?? throw new ArgumentNullException(nameof(byond));
|
||||
|
||||
@@ -14,9 +14,12 @@ using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Compiler;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Components.Interop;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
@@ -255,6 +258,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (Running)
|
||||
throw new JobException("Watchdog already running!");
|
||||
|
||||
if (!DmbFactory.DmbAvailable)
|
||||
throw new JobException("Corrupted compilation, please redeploy!");
|
||||
|
||||
// this is necessary, the monitor could be in it's sleep loop trying to restart, if so cancel THAT monitor and start our own with blackjack and hookers
|
||||
Task chatTask;
|
||||
if (startMonitor && await StopMonitor().ConfigureAwait(false))
|
||||
@@ -423,6 +429,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> HandleEvent(EventType eventType, IEnumerable<string> parameters, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!Running)
|
||||
return true;
|
||||
|
||||
string results;
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
|
||||
@@ -4,78 +4,140 @@ using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Compiler;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class WatchdogFactory : IWatchdogFactory
|
||||
class WatchdogFactory : IWatchdogFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IServerControl"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly IServerControl serverControl;
|
||||
protected IServerControl ServerControl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILoggerFactory"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly ILoggerFactory loggerFactory;
|
||||
protected ILoggerFactory LoggerFactory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseContextFactory databaseContextFactory;
|
||||
protected IDatabaseContextFactory DatabaseContextFactory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IByondTopicSender"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly IByondTopicSender byondTopicSender;
|
||||
protected IByondTopicSender ByondTopicSender { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IJobManager"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly IJobManager jobManager;
|
||||
protected IJobManager JobManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly IAsyncDelayer asyncDelayer;
|
||||
protected IAsyncDelayer AsyncDelayer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> for the <see cref="WatchdogFactory"/>
|
||||
/// The <see cref="Configuration.GeneralConfiguration"/> for the <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
protected GeneralConfiguration GeneralConfiguration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="WatchdogFactory"/>
|
||||
/// </summary>
|
||||
/// <param name="serverControl">The value of <see cref="serverControl"/></param>
|
||||
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="databaseContextFactory"/></param>
|
||||
/// <param name="byondTopicSender">The value of <see cref="byondTopicSender"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="jobManager"/></param>
|
||||
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/></param>
|
||||
/// <param name="serverControl">The value of <see cref="ServerControl"/></param>
|
||||
/// <param name="loggerFactory">The value of <see cref="LoggerFactory"/></param>
|
||||
/// <param name="databaseContextFactory">The value of <see cref="DatabaseContextFactory"/></param>
|
||||
/// <param name="byondTopicSender">The value of <see cref="ByondTopicSender"/></param>
|
||||
/// <param name="jobManager">The value of <see cref="JobManager"/></param>
|
||||
/// <param name="asyncDelayer">The value of <see cref="AsyncDelayer"/></param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="GeneralConfiguration"/></param>
|
||||
public WatchdogFactory(IServerControl serverControl, ILoggerFactory loggerFactory, IDatabaseContextFactory databaseContextFactory, IByondTopicSender byondTopicSender, IJobManager jobManager, IAsyncDelayer asyncDelayer, IOptions<GeneralConfiguration> generalConfigurationOptions)
|
||||
{
|
||||
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
this.loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
this.databaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
this.byondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
|
||||
this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
ServerControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
LoggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
|
||||
DatabaseContextFactory = databaseContextFactory ?? throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
ByondTopicSender = byondTopicSender ?? throw new ArgumentNullException(nameof(byondTopicSender));
|
||||
JobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager));
|
||||
AsyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
|
||||
GeneralConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IWatchdog CreateWatchdog(IChat chat, IDmbFactory dmbFactory, IReattachInfoHandler reattachInfoHandler, IEventConsumer eventConsumer, ISessionControllerFactory sessionControllerFactory, Api.Models.Instance instance, DreamDaemonSettings settings)
|
||||
public IWatchdog CreateWatchdog(
|
||||
IChat chat,
|
||||
IDmbFactory dmbFactory,
|
||||
IReattachInfoHandler reattachInfoHandler,
|
||||
IEventConsumer eventConsumer,
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IIOManager ioManager,
|
||||
Api.Models.Instance instance,
|
||||
DreamDaemonSettings settings)
|
||||
{
|
||||
if (generalConfiguration.UseExperimentalWatchdog)
|
||||
return new ExperimentalWatchdog(chat, sessionControllerFactory, dmbFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, serverControl, asyncDelayer, loggerFactory.CreateLogger<ExperimentalWatchdog>(), settings, instance, settings.AutoStart.Value);
|
||||
if (GeneralConfiguration.UseExperimentalWatchdog)
|
||||
return new ExperimentalWatchdog(
|
||||
chat,
|
||||
sessionControllerFactory,
|
||||
dmbFactory,
|
||||
reattachInfoHandler,
|
||||
DatabaseContextFactory,
|
||||
ByondTopicSender,
|
||||
eventConsumer,
|
||||
JobManager,
|
||||
ServerControl,
|
||||
AsyncDelayer,
|
||||
LoggerFactory.CreateLogger<ExperimentalWatchdog>(),
|
||||
settings,
|
||||
instance,
|
||||
settings.AutoStart.Value);
|
||||
|
||||
return new BasicWatchdog(chat, sessionControllerFactory, dmbFactory, reattachInfoHandler, databaseContextFactory, byondTopicSender, eventConsumer, jobManager, serverControl, asyncDelayer, loggerFactory.CreateLogger<BasicWatchdog>(), settings, instance, settings.AutoStart.Value);
|
||||
return CreateNonExperimentalWatchdog(chat, dmbFactory, reattachInfoHandler, eventConsumer, sessionControllerFactory, ioManager, instance, settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="IWatchdog"/> that isn't the <see cref="ExperimentalWatchdog"/>.
|
||||
/// </summary>
|
||||
/// <param name="chat">The <see cref="IChat"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="IWatchdog"/> with</param>
|
||||
/// <param name="reattachInfoHandler">The <see cref="IReattachInfoHandler"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="eventConsumer">The <see cref="IEventConsumer"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="IWatchdog"/>.</param>
|
||||
/// <param name="instance">The <see cref="Instance"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <param name="settings">The initial <see cref="DreamDaemonSettings"/> for the <see cref="IWatchdog"/></param>
|
||||
/// <returns>A new <see cref="IWatchdog"/></returns>
|
||||
protected virtual IWatchdog CreateNonExperimentalWatchdog(
|
||||
IChat chat,
|
||||
IDmbFactory dmbFactory,
|
||||
IReattachInfoHandler reattachInfoHandler,
|
||||
IEventConsumer eventConsumer,
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IIOManager ioManager,
|
||||
Api.Models.Instance instance,
|
||||
DreamDaemonSettings settings)
|
||||
=> new BasicWatchdog(
|
||||
chat,
|
||||
sessionControllerFactory,
|
||||
dmbFactory,
|
||||
reattachInfoHandler,
|
||||
DatabaseContextFactory,
|
||||
ByondTopicSender,
|
||||
eventConsumer,
|
||||
JobManager,
|
||||
ServerControl,
|
||||
AsyncDelayer,
|
||||
LoggerFactory.CreateLogger<BasicWatchdog>(),
|
||||
settings,
|
||||
instance,
|
||||
settings.AutoStart.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
using Byond.TopicSender;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
/// <summary>
|
||||
/// A version of the <see cref="BasicWatchdog"/> that, instead of killing servers for updates, uses the wonders of symlinks to swap out changes without killing DreamDaemon.
|
||||
/// </summary>
|
||||
sealed class WindowsWatchdog : BasicWatchdog
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="WindowsWatchdog"/>.
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ISymlinkFactory"/> for the <see cref="WindowsWatchdog"/>.
|
||||
/// </summary>
|
||||
readonly ISymlinkFactory symlinkFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="WindowsSwappableDmbProvider"/> for <see cref="WatchdogBase.LastLaunchParameters"/>.
|
||||
/// </summary>
|
||||
WindowsSwappableDmbProvider activeSwappable;
|
||||
|
||||
/// <summary>
|
||||
/// The active <see cref="WindowsSwappableDmbProvider"/> for <see cref="WatchdogBase.ActiveLaunchParameters"/>.
|
||||
/// </summary>
|
||||
WindowsSwappableDmbProvider pendingSwappable;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowsWatchdog"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="chat">The <see cref="IChat"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="sessionControllerFactory">The <see cref="ISessionControllerFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="dmbFactory">The <see cref="IDmbFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="reattachInfoHandler">The <see cref="IReattachInfoHandler"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="byondTopicSender">The <see cref="IByondTopicSender"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="eventConsumer">The <see cref="IEventConsumer"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
|
||||
/// <param name="symlinkFactory">The value of <see cref="symlinkFactory"/>.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="initialLaunchParameters">The <see cref="DreamDaemonLaunchParameters"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="instance">The <see cref="Api.Models.Instance"/> for the <see cref="WatchdogBase"/>.</param>
|
||||
/// <param name="autoStart">The autostart value for the <see cref="WatchdogBase"/>.</param>
|
||||
public WindowsWatchdog(
|
||||
IChat chat,
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IDmbFactory dmbFactory,
|
||||
IReattachInfoHandler reattachInfoHandler,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IByondTopicSender byondTopicSender,
|
||||
IEventConsumer eventConsumer,
|
||||
IJobManager jobManager,
|
||||
IServerControl serverControl,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
IIOManager ioManager,
|
||||
ISymlinkFactory symlinkFactory,
|
||||
ILogger<WindowsWatchdog> logger,
|
||||
DreamDaemonLaunchParameters initialLaunchParameters,
|
||||
Api.Models.Instance instance, bool autoStart)
|
||||
: base(
|
||||
chat,
|
||||
sessionControllerFactory,
|
||||
dmbFactory,
|
||||
reattachInfoHandler,
|
||||
databaseContextFactory,
|
||||
byondTopicSender,
|
||||
eventConsumer,
|
||||
jobManager,
|
||||
serverControl,
|
||||
asyncDelayer,
|
||||
logger,
|
||||
initialLaunchParameters,
|
||||
instance,
|
||||
autoStart)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory));
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DisposeAndNullControllers()
|
||||
{
|
||||
base.DisposeAndNullControllers();
|
||||
|
||||
// If we reach this point, we can guarantee PrepServerForLaunch will be called before starting again.
|
||||
activeSwappable = null;
|
||||
pendingSwappable?.Dispose();
|
||||
pendingSwappable = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override MonitorAction HandleNormalReboot()
|
||||
{
|
||||
Debug.Assert(activeSwappable != null, "Expected activeSwappable to not be null!");
|
||||
if (pendingSwappable != null)
|
||||
{
|
||||
Logger.LogTrace("Replacing activeSwappable with pendingSwappable");
|
||||
Server.ReplaceDmbProvider(pendingSwappable);
|
||||
activeSwappable = pendingSwappable;
|
||||
pendingSwappable = null;
|
||||
}
|
||||
|
||||
return MonitorAction.Continue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task HandleNewDmbAvailable(CancellationToken cancellationToken)
|
||||
{
|
||||
IDmbProvider compileJobProvider = DmbFactory.LockNextDmb(1);
|
||||
WindowsSwappableDmbProvider windowsProvider = null;
|
||||
try
|
||||
{
|
||||
windowsProvider = new WindowsSwappableDmbProvider(compileJobProvider, ioManager, symlinkFactory);
|
||||
|
||||
Logger.LogDebug("Swapping to compile job {0}...", windowsProvider.CompileJob.Id);
|
||||
Server.Suspend();
|
||||
await windowsProvider.MakeActive(cancellationToken).ConfigureAwait(false);
|
||||
Server.Resume();
|
||||
}
|
||||
catch
|
||||
{
|
||||
IDmbProvider providerToDispose = windowsProvider ?? compileJobProvider;
|
||||
providerToDispose.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
pendingSwappable?.Dispose();
|
||||
pendingSwappable = windowsProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<IDmbProvider> PrepServerForLaunch(IDmbProvider dmbToUse, CancellationToken cancellationToken)
|
||||
{
|
||||
Debug.Assert(activeSwappable == null, "Expected swappableDmbProvider to be null!");
|
||||
|
||||
Logger.LogTrace("Prep for server launch. pendingSwappable is {0}avaiable", pendingSwappable == null ? "not " : String.Empty);
|
||||
|
||||
activeSwappable = pendingSwappable ?? new WindowsSwappableDmbProvider(dmbToUse, ioManager, symlinkFactory);
|
||||
pendingSwappable = null;
|
||||
|
||||
try
|
||||
{
|
||||
await activeSwappable.MakeActive(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// We won't worry about disposing activeSwappable here as we can't dispose dmbToUse here.
|
||||
activeSwappable = null;
|
||||
throw;
|
||||
}
|
||||
|
||||
return activeSwappable;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using Byond.TopicSender;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Watchdog
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IWatchdogFactory"/> for creating <see cref="WindowsWatchdog"/>s.
|
||||
/// </summary>
|
||||
sealed class WindowsWatchdogFactory : WatchdogFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ISymlinkFactory"/> for the <see cref="WindowsWatchdogFactory"/>.
|
||||
/// </summary>
|
||||
readonly ISymlinkFactory symlinkFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowsWatchdogFactory"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="serverControl">The <see cref="IServerControl"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="byondTopicSender">The <see cref="IByondTopicSender"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="jobManager">The <see cref="IJobManager"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="asyncDelayer">The <see cref="IAsyncDelayer"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
/// <param name="symlinkFactory">The value of <see cref="symlinkFactory"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> for <see cref="GeneralConfiguration"/> for the <see cref="WatchdogFactory"/>.</param>
|
||||
public WindowsWatchdogFactory(
|
||||
IServerControl serverControl,
|
||||
ILoggerFactory loggerFactory,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
IByondTopicSender byondTopicSender,
|
||||
IJobManager jobManager,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
ISymlinkFactory symlinkFactory,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions)
|
||||
: base(
|
||||
serverControl,
|
||||
loggerFactory,
|
||||
databaseContextFactory,
|
||||
byondTopicSender,
|
||||
jobManager,
|
||||
asyncDelayer,
|
||||
generalConfigurationOptions)
|
||||
{
|
||||
this.symlinkFactory = symlinkFactory ?? throw new ArgumentNullException(nameof(symlinkFactory));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IWatchdog CreateNonExperimentalWatchdog(
|
||||
IChat chat,
|
||||
IDmbFactory dmbFactory,
|
||||
IReattachInfoHandler reattachInfoHandler,
|
||||
IEventConsumer eventConsumer,
|
||||
ISessionControllerFactory sessionControllerFactory,
|
||||
IIOManager ioManager,
|
||||
Api.Models.Instance instance,
|
||||
DreamDaemonSettings settings)
|
||||
=> new WindowsWatchdog(
|
||||
chat,
|
||||
sessionControllerFactory,
|
||||
dmbFactory,
|
||||
reattachInfoHandler,
|
||||
DatabaseContextFactory,
|
||||
ByondTopicSender,
|
||||
eventConsumer,
|
||||
JobManager,
|
||||
ServerControl,
|
||||
AsyncDelayer,
|
||||
ioManager,
|
||||
symlinkFactory,
|
||||
LoggerFactory.CreateLogger<WindowsWatchdog>(),
|
||||
settings,
|
||||
instance,
|
||||
settings.AutoStart.Value);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using Newtonsoft.Json.Converters;
|
||||
namespace Tgstation.Server.Host.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration options for the <see cref="Models.DatabaseContext{TParentContext}"/>
|
||||
/// Configuration options for the <see cref="Database.DatabaseContext{TParentContext}"/>
|
||||
/// </summary>
|
||||
sealed class DatabaseConfiguration
|
||||
{
|
||||
@@ -35,7 +35,7 @@ namespace Tgstation.Server.Host.Configuration
|
||||
public bool DropDatabase { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="string"/> form of the <see cref="System.Version"/> of a target MySQL/MariaDB server
|
||||
/// The <see cref="string"/> form of the <see cref="global::System.Version"/> of a target MySQL/MariaDB server
|
||||
/// </summary>
|
||||
public string MySqlServerVersion { get; set; }
|
||||
}
|
||||
|
||||
@@ -58,5 +58,10 @@ namespace Tgstation.Server.Host.Configuration
|
||||
/// If the <see cref="Components.Watchdog.ExperimentalWatchdog"/> should be used.
|
||||
/// </summary>
|
||||
public bool UseExperimentalWatchdog { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="Components.Watchdog.WindowsWatchdog"/> should not be used if it is available.
|
||||
/// </summary>
|
||||
public bool UseBasicWatchdogOnWindows { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
public long GitHubRepositoryId { get; set; } = DefaultGitHubRepositoryId;
|
||||
|
||||
/// <summary>
|
||||
/// Prefix before the <see cref="System.Version"/> of TGS published in git tags
|
||||
/// Prefix before the <see cref="global::System.Version"/> of TGS published in git tags
|
||||
/// </summary>
|
||||
public string GitTagPrefix { get; set; } = DefaultGitTagPrefix;
|
||||
|
||||
|
||||
@@ -15,9 +15,11 @@ using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
@@ -79,7 +81,23 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/></param>
|
||||
/// <param name="updatesConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="updatesConfiguration"/></param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing value of <see cref="generalConfiguration"/></param>
|
||||
public AdministrationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IGitHubClientFactory gitHubClientFactory, IServerControl serverUpdater, IApplication application, IIOManager ioManager, IPlatformIdentifier platformIdentifier, ILogger<AdministrationController> logger, IOptions<UpdatesConfiguration> updatesConfigurationOptions, IOptions<GeneralConfiguration> generalConfigurationOptions) : base(databaseContext, authenticationContextFactory, logger, false, true)
|
||||
public AdministrationController(
|
||||
IDatabaseContext databaseContext,
|
||||
IAuthenticationContextFactory authenticationContextFactory,
|
||||
IGitHubClientFactory gitHubClientFactory,
|
||||
IServerControl serverUpdater,
|
||||
IApplication application,
|
||||
IIOManager ioManager,
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
ILogger<AdministrationController> logger,
|
||||
IOptions<UpdatesConfiguration> updatesConfigurationOptions,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions)
|
||||
: base(
|
||||
databaseContext,
|
||||
authenticationContextFactory,
|
||||
logger,
|
||||
false,
|
||||
true)
|
||||
{
|
||||
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
|
||||
this.serverUpdater = serverUpdater ?? throw new ArgumentNullException(nameof(serverUpdater));
|
||||
@@ -215,10 +233,18 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="model">The model containing the <see cref="Administration.NewVersion"/> to update to.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> for the operation.</returns>
|
||||
/// <response code="202">Update has been started successfully.</response>
|
||||
/// <response code="410">The requested version could not be found.</response>
|
||||
/// <response code="422">Upgrade operations are unavailable due to the launch configuration of TGS.</response>
|
||||
/// <response code="424">A GitHub rate limit was encountered.</response>
|
||||
/// <response code="429">A GitHub API error occurred.</response>
|
||||
[HttpPost]
|
||||
[TgsAuthorize(AdministrationRights.ChangeVersion)]
|
||||
[ProducesResponseType(202)]
|
||||
[ProducesResponseType(410)]
|
||||
[ProducesResponseType(typeof(ErrorMessage), 422)]
|
||||
[ProducesResponseType(424)]
|
||||
[ProducesResponseType(typeof(ErrorMessage), 429)]
|
||||
public async Task<IActionResult> Update([FromBody] Administration model, CancellationToken cancellationToken)
|
||||
{
|
||||
if (model == null)
|
||||
@@ -240,12 +266,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to restart the server
|
||||
/// Attempts to restart the server.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the request</returns>
|
||||
/// <response code="200">Restart begun successfully.</response>
|
||||
/// <response code="422">Restart operations are unavailable due to the launch configuration of TGS.</response>
|
||||
[HttpDelete("{id}")]
|
||||
[HttpDelete]
|
||||
[TgsAuthorize(AdministrationRights.RestartHost)]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(typeof(ErrorMessage), 422)]
|
||||
|
||||
@@ -9,7 +9,7 @@ using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
@@ -9,7 +9,8 @@ using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Z.EntityFramework.Plus;
|
||||
|
||||
@@ -10,6 +10,7 @@ using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
@@ -13,7 +13,8 @@ using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
@@ -150,7 +151,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation.</returns>
|
||||
/// <response code="200">Watchdog terminated.</response>
|
||||
[HttpDelete("{id}")]
|
||||
[HttpDelete]
|
||||
[TgsAuthorize(DreamDaemonRights.Shutdown)]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> Delete(CancellationToken cancellationToken)
|
||||
|
||||
@@ -11,7 +11,8 @@ using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Wangkanai.Detection;
|
||||
@@ -164,25 +165,41 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
using (systemIdentity)
|
||||
{
|
||||
// Get the user from the database
|
||||
IQueryable<User> query;
|
||||
string canonicalName = ApiHeaders.Username.ToUpperInvariant();
|
||||
if (systemIdentity == null)
|
||||
query = DatabaseContext.Users.Where(x => x.CanonicalName == ApiHeaders.Username.ToUpperInvariant());
|
||||
query = DatabaseContext.Users.Where(x => x.CanonicalName == canonicalName);
|
||||
else
|
||||
query = DatabaseContext.Users.Where(x => x.SystemIdentifier == systemIdentity.Uid);
|
||||
var user = await query.Select(x => new User
|
||||
query = DatabaseContext.Users.Where(x => x.CanonicalName == canonicalName || x.SystemIdentifier == systemIdentity.Uid);
|
||||
var users = await query.Select(x => new User
|
||||
{
|
||||
Id = x.Id,
|
||||
PasswordHash = x.PasswordHash,
|
||||
Enabled = x.Enabled,
|
||||
Name = x.Name
|
||||
}).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
}).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Pick the DB user first
|
||||
var user = users
|
||||
.OrderByDescending(dbUser => dbUser.PasswordHash != null)
|
||||
.FirstOrDefault();
|
||||
|
||||
// No user? You're not allowed
|
||||
if (user == null)
|
||||
return Unauthorized();
|
||||
|
||||
if (systemIdentity == null)
|
||||
// A system user may have had their name AND password changed to one in our DB...
|
||||
// Or a DB user was created that had the same user/pass as a system user
|
||||
// Dumb admins...
|
||||
// FALLBACK TO THE DB USER HERE, DO NOT REVEAL A SYSTEM LOGIN!!!
|
||||
// This of course, allows system users to discover TGS users in this (HIGHLY IMPROBABLE) case but that is not our fault
|
||||
var originalHash = user.PasswordHash;
|
||||
var isDbUser = originalHash != null;
|
||||
bool usingSystemIdentity = systemIdentity != null && !isDbUser;
|
||||
if (!usingSystemIdentity)
|
||||
{
|
||||
var originalHash = user.PasswordHash;
|
||||
// DB User password check and update
|
||||
if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password))
|
||||
return Unauthorized();
|
||||
if (user.PasswordHash != originalHash)
|
||||
@@ -197,10 +214,9 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// check if the name changed and updoot accordingly
|
||||
else if (systemIdentity.Username != user.Name)
|
||||
{
|
||||
// System identity username change update
|
||||
Logger.LogDebug("User ID {0}'s system identity needs a refresh, updating database.", user.Id);
|
||||
DatabaseContext.Users.Attach(user);
|
||||
user.Name = systemIdentity.Username;
|
||||
@@ -216,7 +232,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
|
||||
var token = await tokenFactory.CreateToken(user, cancellationToken).ConfigureAwait(false);
|
||||
if (systemIdentity != null)
|
||||
if (usingSystemIdentity)
|
||||
{
|
||||
// expire the identity slightly after the auth token in case of lag
|
||||
var identExpiry = token.ExpiresAt.Value;
|
||||
|
||||
@@ -16,9 +16,12 @@ using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Z.EntityFramework.Plus;
|
||||
|
||||
@@ -8,7 +8,8 @@ using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
@@ -230,7 +232,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation</returns>
|
||||
/// <response code="202">Job to delete the repository created successfully.</response>
|
||||
/// <response code="410">Instance no longer available.</response>
|
||||
[HttpDelete("{id}")]
|
||||
[HttpDelete]
|
||||
[TgsAuthorize(RepositoryRights.Delete)]
|
||||
[ProducesResponseType(typeof(Repository), 202)]
|
||||
[ProducesResponseType(410)]
|
||||
|
||||
@@ -12,6 +12,7 @@ using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
@@ -75,7 +76,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Attempt to change the password of a given <paramref name="dbUser"/>.
|
||||
/// </summary>
|
||||
/// <param name="dbUser">The <see cref="Models.User"/> to update.</param>
|
||||
/// <param name="dbUser">The user to update.</param>
|
||||
/// <param name="newPassword">The new password.</param>
|
||||
/// <returns><see langword="null"/> on success, <see cref="BadRequestObjectResult"/> if <paramref name="newPassword"/> is too short.</returns>
|
||||
BadRequestObjectResult TrySetPassword(Models.User dbUser, string newPassword)
|
||||
|
||||
@@ -12,8 +12,6 @@ using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Serilog;
|
||||
@@ -31,10 +29,12 @@ using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Controllers;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
@@ -61,6 +61,11 @@ namespace Tgstation.Server.Host.Core
|
||||
/// </summary>
|
||||
readonly IAssemblyInformationProvider assemblyInformationProvider;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="Application"/>.
|
||||
/// </summary>
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Microsoft.AspNetCore.Hosting.IHostingEnvironment"/> for the <see cref="Application"/>
|
||||
/// </summary>
|
||||
@@ -82,14 +87,17 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <param name="configuration">The value of <see cref="configuration"/></param>
|
||||
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> for the <see cref="Application"/>.</param>
|
||||
/// <param name="hostingEnvironment">The value of <see cref="hostingEnvironment"/></param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
|
||||
public Application(
|
||||
IConfiguration configuration,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment)
|
||||
Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment,
|
||||
IIOManager ioManager)
|
||||
{
|
||||
this.configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
|
||||
this.hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
|
||||
startupTcs = new TaskCompletionSource<object>();
|
||||
|
||||
@@ -123,9 +131,8 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddLogging();
|
||||
|
||||
// other stuff needed for for setup wizard and configuration
|
||||
services.AddSingleton<IIOManager, DefaultIOManager>();
|
||||
services.AddSingleton<IConsole, IO.Console>();
|
||||
services.AddSingleton<IDBConnectionFactory, DBConnectionFactory>();
|
||||
services.AddSingleton<IDatabaseConnectionFactory, DatabaseConnectionFactory>();
|
||||
services.AddSingleton<ISetupWizard, SetupWizard>();
|
||||
services.AddSingleton<IPlatformIdentifier, PlatformIdentifier>();
|
||||
services.AddSingleton<IAsyncDelayer, AsyncDelayer>();
|
||||
@@ -134,7 +141,6 @@ namespace Tgstation.Server.Host.Core
|
||||
DatabaseConfiguration databaseConfiguration;
|
||||
FileLoggingConfiguration fileLoggingConfiguration;
|
||||
ControlPanelConfiguration controlPanelConfiguration;
|
||||
IIOManager ioManager;
|
||||
IPlatformIdentifier platformIdentifier;
|
||||
|
||||
// temporarily build the service provider in it's current state
|
||||
@@ -164,7 +170,6 @@ namespace Tgstation.Server.Host.Core
|
||||
var controlPanelOptions = provider.GetRequiredService<IOptions<ControlPanelConfiguration>>();
|
||||
controlPanelConfiguration = controlPanelOptions.Value;
|
||||
|
||||
ioManager = provider.GetRequiredService<IIOManager>();
|
||||
platformIdentifier = provider.GetRequiredService<IPlatformIdentifier>();
|
||||
}
|
||||
|
||||
@@ -254,43 +259,12 @@ namespace Tgstation.Server.Host.Core
|
||||
});
|
||||
|
||||
if (hostingEnvironment.IsDevelopment())
|
||||
services.AddSwaggerGen(
|
||||
c =>
|
||||
{
|
||||
c.SwaggerDoc(
|
||||
"v1",
|
||||
new OpenApiInfo
|
||||
{
|
||||
Title = "TGS API",
|
||||
Version = "v4"
|
||||
});
|
||||
|
||||
// Important to do this before applying our own filters
|
||||
// Otherwise we'll get NullReferenceExceptions on parameters to be setup in our document filter
|
||||
var assemblyLocation = assemblyInformationProvider.Path;
|
||||
var filePath = ioManager.ConcatPath(ioManager.GetDirectoryName(assemblyLocation), String.Concat(ioManager.GetFileNameWithoutExtension(assemblyLocation), ".xml"));
|
||||
c.IncludeXmlComments(filePath);
|
||||
|
||||
c.OperationFilter<TgsOpenApiFilters>();
|
||||
c.DocumentFilter<TgsOpenApiFilters>();
|
||||
|
||||
c.AddSecurityDefinition(TgsOpenApiFilters.PasswordSecuritySchemeId, new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.Http,
|
||||
Name = HeaderNames.Authorization,
|
||||
Scheme = ApiHeaders.BasicAuthenticationScheme
|
||||
});
|
||||
|
||||
c.AddSecurityDefinition(TgsOpenApiFilters.TokenSecuritySchemeId, new OpenApiSecurityScheme
|
||||
{
|
||||
BearerFormat = "JWT",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.Http,
|
||||
Name = HeaderNames.Authorization,
|
||||
Scheme = ApiHeaders.JwtAuthenticationScheme
|
||||
});
|
||||
});
|
||||
{
|
||||
string GetDocumentationFilePath(string assemblyLocation) => ioManager.ConcatPath(ioManager.GetDirectoryName(assemblyLocation), String.Concat(ioManager.GetFileNameWithoutExtension(assemblyLocation), ".xml"));
|
||||
var assemblyDocumentationPath = GetDocumentationFilePath(assemblyInformationProvider.Path);
|
||||
var apiDocumentationPath = GetDocumentationFilePath(typeof(ApiHeaders).Assembly.Location);
|
||||
services.AddSwaggerGen(genOptions => SwaggerConfiguration.Configure(genOptions, assemblyDocumentationPath, apiDocumentationPath));
|
||||
}
|
||||
|
||||
// enable browser detection
|
||||
services.AddDetectionCore().AddBrowser();
|
||||
@@ -338,6 +312,11 @@ namespace Tgstation.Server.Host.Core
|
||||
// configure platform specific services
|
||||
if (platformIdentifier.IsWindows)
|
||||
{
|
||||
if (generalConfiguration.UseBasicWatchdogOnWindows)
|
||||
services.AddSingleton<IWatchdogFactory, WatchdogFactory>();
|
||||
else
|
||||
services.AddSingleton<IWatchdogFactory, WindowsWatchdogFactory>();
|
||||
|
||||
services.AddSingleton<ISystemIdentityFactory, WindowsSystemIdentityFactory>();
|
||||
services.AddSingleton<ISymlinkFactory, WindowsSymlinkFactory>();
|
||||
services.AddSingleton<IByondInstaller, WindowsByondInstaller>();
|
||||
@@ -349,6 +328,7 @@ namespace Tgstation.Server.Host.Core
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddSingleton<IWatchdogFactory, WatchdogFactory>();
|
||||
services.AddSingleton<ISystemIdentityFactory, PosixSystemIdentityFactory>();
|
||||
services.AddSingleton<ISymlinkFactory, PosixSymlinkFactory>();
|
||||
services.AddSingleton<IByondInstaller, PosixByondInstaller>();
|
||||
@@ -370,7 +350,6 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<ICredentialsProvider, CredentialsProvider>();
|
||||
services.AddSingleton<IProviderFactory, ProviderFactory>();
|
||||
services.AddSingleton<IChatFactory, ChatFactory>();
|
||||
services.AddSingleton<IWatchdogFactory, WatchdogFactory>();
|
||||
services.AddSingleton<IInstanceFactory, InstanceFactory>();
|
||||
|
||||
// configure root services
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using Microsoft.OpenApi;
|
||||
using Microsoft.OpenApi.Interfaces;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.OpenApi.Writers;
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the "x-enum-varnames" OpenAPI 3.0 extension.
|
||||
/// </summary>
|
||||
sealed class OpenApiEnumVarNamesExtension : IOpenApiExtension
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Type"/> of the <see cref="Enum"/> being described.
|
||||
/// </summary>
|
||||
readonly Type enumType;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="OpenApiEnumVarNamesExtension"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="enumType">The value of <see cref="enumType"/>,</param>
|
||||
private OpenApiEnumVarNamesExtension(Type enumType)
|
||||
{
|
||||
this.enumType = enumType ?? throw new ArgumentNullException(nameof(enumType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies the extension to a give <paramref name="openApiSchema"/>.
|
||||
/// </summary>
|
||||
/// <param name="openApiSchema">The <see cref="OpenApiSchema"/> to apply to.</param>
|
||||
/// <param name="enumType">The <see cref="Type"/> of the <see cref="Enum"/> being described.</param>
|
||||
public static void Apply(OpenApiSchema openApiSchema, Type enumType)
|
||||
{
|
||||
if (openApiSchema == null)
|
||||
throw new ArgumentNullException(nameof(openApiSchema));
|
||||
|
||||
openApiSchema.Extensions.Add("x-enum-varnames", new OpenApiEnumVarNamesExtension(enumType));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
|
||||
{
|
||||
if (writer == null)
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
|
||||
if (specVersion != OpenApiSpecVersion.OpenApi3_0)
|
||||
throw new InvalidOperationException("This extension only applies to OpenAPI 3.0!");
|
||||
|
||||
writer.WriteStartArray();
|
||||
foreach (var enumValue in Enum.GetValues(enumType))
|
||||
writer.WriteValue(enumValue.ToString());
|
||||
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,9 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
@@ -42,9 +44,9 @@ namespace Tgstation.Server.Host.Core
|
||||
readonly IApplication application;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDBConnectionFactory"/> for the <see cref="SetupWizard"/>
|
||||
/// The <see cref="IDatabaseConnectionFactory"/> for the <see cref="SetupWizard"/>
|
||||
/// </summary>
|
||||
readonly IDBConnectionFactory dbConnectionFactory;
|
||||
readonly IDatabaseConnectionFactory dbConnectionFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPlatformIdentifier"/> for the <see cref="SetupWizard"/>
|
||||
@@ -78,7 +80,16 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/></param>
|
||||
public SetupWizard(IIOManager ioManager, IConsole console, IHostingEnvironment hostingEnvironment, IApplication application, IDBConnectionFactory dbConnectionFactory, IPlatformIdentifier platformIdentifier, IAsyncDelayer asyncDelayer, ILogger<SetupWizard> logger, IOptions<GeneralConfiguration> generalConfigurationOptions)
|
||||
public SetupWizard(
|
||||
IIOManager ioManager,
|
||||
IConsole console,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IApplication application,
|
||||
IDatabaseConnectionFactory dbConnectionFactory,
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
IAsyncDelayer asyncDelayer,
|
||||
ILogger<SetupWizard> logger,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.console = console ?? throw new ArgumentNullException(nameof(console));
|
||||
@@ -408,6 +419,11 @@ namespace Tgstation.Server.Host.Core
|
||||
newGeneralConfiguration.GitHubAccessToken = await console.ReadLineAsync(true, cancellationToken).ConfigureAwait(false);
|
||||
if (String.IsNullOrWhiteSpace(newGeneralConfiguration.GitHubAccessToken))
|
||||
newGeneralConfiguration.GitHubAccessToken = null;
|
||||
|
||||
newGeneralConfiguration.UseExperimentalWatchdog = await PromptYesNo("Use the experimental watchdog (NOT RECOMMENDED)? (y/n): ", cancellationToken).ConfigureAwait(false);
|
||||
if (!newGeneralConfiguration.UseExperimentalWatchdog && platformIdentifier.IsWindows)
|
||||
newGeneralConfiguration.UseBasicWatchdogOnWindows = !await PromptYesNo("Use the TGS3 style Windows watchdog (RECOMMENDED)? (y/n): ", cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return newGeneralConfiguration;
|
||||
}
|
||||
|
||||
|
||||
+175
-82
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
@@ -9,23 +10,140 @@ using System.Net;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Controllers;
|
||||
|
||||
namespace Tgstation.Server.Host.Controllers
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IOperationFilter"/> and <see cref="IDocumentFilter"/> for the server.
|
||||
/// Implements various filters for <see cref="Swashbuckle"/>.
|
||||
/// </summary>
|
||||
sealed class TgsOpenApiFilters : IOperationFilter, IDocumentFilter
|
||||
sealed class SwaggerConfiguration : IOperationFilter, IDocumentFilter, ISchemaFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="OpenApiSecurityScheme"/> name for password authentication.
|
||||
/// </summary>
|
||||
public const string PasswordSecuritySchemeId = "Password_Login_Scheme";
|
||||
const string PasswordSecuritySchemeId = "Password_Login_Scheme";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="OpenApiSecurityScheme"/> name for token authentication.
|
||||
/// </summary>
|
||||
public const string TokenSecuritySchemeId = "Token_Authorization_Scheme";
|
||||
const string TokenSecuritySchemeId = "Token_Authorization_Scheme";
|
||||
|
||||
static void AddDefaultResponses(OpenApiDocument document)
|
||||
{
|
||||
var errorMessageContent = new Dictionary<string, OpenApiMediaType>
|
||||
{
|
||||
{
|
||||
ApiHeaders.ApplicationJson,
|
||||
new OpenApiMediaType
|
||||
{
|
||||
Schema = new OpenApiSchema
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Id = nameof(ErrorMessage),
|
||||
Type = ReferenceType.Schema
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void AddDefaultResponse(HttpStatusCode code, OpenApiResponse concrete)
|
||||
{
|
||||
string responseKey = $"{(int)code}";
|
||||
|
||||
document.Components.Responses.Add(responseKey, concrete);
|
||||
|
||||
var referenceResponse = new OpenApiResponse
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.Response,
|
||||
Id = responseKey
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var operation in document.Paths.SelectMany(path => path.Value.Operations))
|
||||
operation.Value.Responses.TryAdd(responseKey, referenceResponse);
|
||||
}
|
||||
|
||||
AddDefaultResponse(HttpStatusCode.BadRequest, new OpenApiResponse
|
||||
{
|
||||
Description = "A badly formatted request was made. See error message for details.",
|
||||
Content = errorMessageContent,
|
||||
});
|
||||
|
||||
AddDefaultResponse(HttpStatusCode.Unauthorized, new OpenApiResponse
|
||||
{
|
||||
Description = "No/invalid token provided."
|
||||
});
|
||||
|
||||
AddDefaultResponse(HttpStatusCode.Forbidden, new OpenApiResponse
|
||||
{
|
||||
Description = "User lacks sufficient permissions for the operation."
|
||||
});
|
||||
|
||||
AddDefaultResponse(HttpStatusCode.Conflict, new OpenApiResponse
|
||||
{
|
||||
Description = "A data integrity check failed while performing the operation. See error message for details.",
|
||||
Content = errorMessageContent
|
||||
});
|
||||
|
||||
AddDefaultResponse(HttpStatusCode.InternalServerError, new OpenApiResponse
|
||||
{
|
||||
Description = "The server encountered an unhandled error. See error message for details.",
|
||||
Content = errorMessageContent
|
||||
});
|
||||
|
||||
AddDefaultResponse(HttpStatusCode.ServiceUnavailable, new OpenApiResponse
|
||||
{
|
||||
Description = "The server may be starting up or shutting down."
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the swagger settings.
|
||||
/// </summary>
|
||||
/// <param name="swaggerGenOptions">The <see cref="SwaggerGenOptions"/> to use.</param>
|
||||
/// <param name="assemblyDocumentationPath">The path to the XML documentation file for the <see cref="Host"/> assembly.</param>
|
||||
/// <param name="apiDocumentationPath">The path to the XML documentation file for the <see cref="Api"/> assembly.</param>
|
||||
public static void Configure(SwaggerGenOptions swaggerGenOptions, string assemblyDocumentationPath, string apiDocumentationPath)
|
||||
{
|
||||
swaggerGenOptions.SwaggerDoc(
|
||||
"v1",
|
||||
new OpenApiInfo
|
||||
{
|
||||
Title = "TGS API",
|
||||
Version = "v4"
|
||||
});
|
||||
|
||||
// Important to do this before applying our own filters
|
||||
// Otherwise we'll get NullReferenceExceptions on parameters to be setup in our document filter
|
||||
swaggerGenOptions.IncludeXmlComments(assemblyDocumentationPath);
|
||||
swaggerGenOptions.IncludeXmlComments(apiDocumentationPath);
|
||||
|
||||
swaggerGenOptions.OperationFilter<SwaggerConfiguration>();
|
||||
swaggerGenOptions.DocumentFilter<SwaggerConfiguration>();
|
||||
swaggerGenOptions.SchemaFilter<SwaggerConfiguration>();
|
||||
|
||||
swaggerGenOptions.AddSecurityDefinition(PasswordSecuritySchemeId, new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.Http,
|
||||
Name = HeaderNames.Authorization,
|
||||
Scheme = ApiHeaders.BasicAuthenticationScheme
|
||||
});
|
||||
|
||||
swaggerGenOptions.AddSecurityDefinition(TokenSecuritySchemeId, new OpenApiSecurityScheme
|
||||
{
|
||||
BearerFormat = "JWT",
|
||||
In = ParameterLocation.Header,
|
||||
Type = SecuritySchemeType.Http,
|
||||
Name = HeaderNames.Authorization,
|
||||
Scheme = ApiHeaders.JwtAuthenticationScheme
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
@@ -35,6 +153,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (context == null)
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
|
||||
operation.OperationId = $"{context.MethodInfo.DeclaringType.Name}.{context.MethodInfo.Name}";
|
||||
|
||||
var authAttributes = context
|
||||
.MethodInfo
|
||||
.DeclaringType
|
||||
@@ -72,7 +192,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.Header,
|
||||
Type = ReferenceType.Parameter,
|
||||
Id = ApiHeaders.InstanceIdHeader
|
||||
}
|
||||
});
|
||||
@@ -105,27 +225,50 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <inheritdoc />
|
||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
{
|
||||
swaggerDoc.Components.Headers.Add(ApiHeaders.InstanceIdHeader, new OpenApiHeader
|
||||
if (swaggerDoc == null)
|
||||
throw new ArgumentNullException(nameof(swaggerDoc));
|
||||
if (context == null)
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
|
||||
swaggerDoc.Components.Parameters.Add(ApiHeaders.InstanceIdHeader, new OpenApiParameter
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Name = ApiHeaders.InstanceIdHeader,
|
||||
Description = "The instance ID being accessed",
|
||||
Required = true,
|
||||
Style = ParameterStyle.Simple
|
||||
Style = ParameterStyle.Simple,
|
||||
Schema = new OpenApiSchema
|
||||
{
|
||||
Type = "integer"
|
||||
}
|
||||
});
|
||||
|
||||
swaggerDoc.Components.Headers.Add(ApiHeaders.ApiVersionHeader, new OpenApiHeader
|
||||
var productHeaderSchema = new OpenApiSchema
|
||||
{
|
||||
Type = "string",
|
||||
Format = "productheader"
|
||||
};
|
||||
|
||||
swaggerDoc.Components.Parameters.Add(ApiHeaders.ApiVersionHeader, new OpenApiParameter
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Name = ApiHeaders.ApiVersionHeader,
|
||||
Description = "The API version being used in the form \"Tgstation.Server.Api/[API version]\"",
|
||||
Required = true,
|
||||
Style = ParameterStyle.Simple,
|
||||
Example = new OpenApiString($"Tgstation.Server.Api/{ApiHeaders.Version}")
|
||||
Example = new OpenApiString($"Tgstation.Server.Api/{ApiHeaders.Version}"),
|
||||
Schema = productHeaderSchema
|
||||
});
|
||||
|
||||
swaggerDoc.Components.Headers.Add(HeaderNames.UserAgent, new OpenApiHeader
|
||||
swaggerDoc.Components.Parameters.Add(HeaderNames.UserAgent, new OpenApiParameter
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Name = HeaderNames.UserAgent,
|
||||
Description = "The user agent of the calling client.",
|
||||
Required = true,
|
||||
Style = ParameterStyle.Simple,
|
||||
Example = new OpenApiString("Your-user-agent/1.0.0.0")
|
||||
Example = new OpenApiString("Your-user-agent/1.0.0.0"),
|
||||
Schema = productHeaderSchema
|
||||
});
|
||||
|
||||
foreach (var operation in swaggerDoc
|
||||
@@ -137,91 +280,41 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.Header,
|
||||
Type = ReferenceType.Parameter,
|
||||
Id = ApiHeaders.ApiVersionHeader
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
operation.Parameters.Add(new OpenApiParameter
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.Header,
|
||||
Type = ReferenceType.Parameter,
|
||||
Id = HeaderNames.UserAgent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var errorMessageContent = new Dictionary<string, OpenApiMediaType>
|
||||
{
|
||||
{
|
||||
ApiHeaders.ApplicationJson,
|
||||
new OpenApiMediaType
|
||||
{
|
||||
Schema = new OpenApiSchema
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Id = nameof(ErrorMessage),
|
||||
Type = ReferenceType.Schema
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
AddDefaultResponses(swaggerDoc);
|
||||
}
|
||||
|
||||
void AddDefaultResponse(HttpStatusCode code, OpenApiResponse concrete)
|
||||
{
|
||||
string responseKey = $"{(int)code}";
|
||||
/// <inheritdoc />
|
||||
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
|
||||
{
|
||||
if (schema == null)
|
||||
throw new ArgumentNullException(nameof(schema));
|
||||
if (context == null)
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
|
||||
swaggerDoc.Components.Responses.Add(responseKey, concrete);
|
||||
if (!schema.Enum?.Any() ?? false)
|
||||
return;
|
||||
|
||||
var referenceResponse = new OpenApiResponse
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.Response,
|
||||
Id = responseKey
|
||||
}
|
||||
};
|
||||
// Could be nullable type, make sure to get the right one
|
||||
Type enumType = context.Type.IsConstructedGenericType
|
||||
? context.Type.GenericTypeArguments.First()
|
||||
: context.Type;
|
||||
|
||||
foreach (var path in swaggerDoc.Paths)
|
||||
foreach (var operation in path.Value.Operations)
|
||||
operation.Value.Responses.TryAdd(responseKey, referenceResponse);
|
||||
}
|
||||
|
||||
AddDefaultResponse(HttpStatusCode.BadRequest, new OpenApiResponse
|
||||
{
|
||||
Description = "A badly formatted request was made. See error message for details.",
|
||||
Content = errorMessageContent,
|
||||
});
|
||||
|
||||
AddDefaultResponse(HttpStatusCode.Unauthorized, new OpenApiResponse
|
||||
{
|
||||
Description = "No/invalid token provided."
|
||||
});
|
||||
|
||||
AddDefaultResponse(HttpStatusCode.Forbidden, new OpenApiResponse
|
||||
{
|
||||
Description = "User lacks sufficient permissions for the operation."
|
||||
});
|
||||
|
||||
AddDefaultResponse(HttpStatusCode.Conflict, new OpenApiResponse
|
||||
{
|
||||
Description = "A data integrity check failed while performing the operation. See error message for details.",
|
||||
Content = errorMessageContent
|
||||
});
|
||||
|
||||
AddDefaultResponse(HttpStatusCode.InternalServerError, new OpenApiResponse
|
||||
{
|
||||
Description = "The server encountered an unhandled error. See error message for details.",
|
||||
Content = errorMessageContent
|
||||
});
|
||||
|
||||
AddDefaultResponse(HttpStatusCode.ServiceUnavailable, new OpenApiResponse
|
||||
{
|
||||
Description = "The server may be starting up or shutting down."
|
||||
});
|
||||
OpenApiEnumVarNamesExtension.Apply(schema, enumType);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -2,13 +2,12 @@
|
||||
using System;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Globalization;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DBConnectionFactory : IDBConnectionFactory
|
||||
sealed class DatabaseConnectionFactory : IDatabaseConnectionFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public DbConnection CreateConnection(string connectionString, DatabaseType databaseType)
|
||||
+2
-1
@@ -10,9 +10,10 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Models.Migrations;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CA1506 // TODO: Decomplexify
|
||||
+1
-2
@@ -1,9 +1,8 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DatabaseContextFactory : IDatabaseContextFactory
|
||||
+8
-4
@@ -4,9 +4,10 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DatabaseSeeder : IDatabaseSeeder
|
||||
@@ -20,7 +21,10 @@ namespace Tgstation.Server.Host.Models
|
||||
/// Construct a <see cref="DatabaseSeeder"/>
|
||||
/// </summary>
|
||||
/// <param name="cryptographySuite">The value of <see cref="cryptographySuite"/></param>
|
||||
public DatabaseSeeder(ICryptographySuite cryptographySuite) => this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
|
||||
public DatabaseSeeder(ICryptographySuite cryptographySuite)
|
||||
{
|
||||
this.cryptographySuite = cryptographySuite ?? throw new ArgumentNullException(nameof(cryptographySuite));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a default admin <see cref="User"/> to a given <paramref name="databaseContext"/>
|
||||
@@ -30,9 +34,9 @@ namespace Tgstation.Server.Host.Models
|
||||
{
|
||||
var admin = new User
|
||||
{
|
||||
AdministrationRights = (AdministrationRights)~0U,
|
||||
AdministrationRights = ~AdministrationRights.None,
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
InstanceManagerRights = (InstanceManagerRights)~0U,
|
||||
InstanceManagerRights = ~InstanceManagerRights.None,
|
||||
Name = Api.Models.User.AdminName,
|
||||
CanonicalName = Api.Models.User.AdminName.ToUpperInvariant(),
|
||||
Enabled = true,
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
namespace Tgstation.Server.Host.Database.Design
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains helpers for creating design time <see cref="DatabaseContext{TParentContext}"/>s
|
||||
+2
-1
@@ -2,9 +2,10 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
namespace Tgstation.Server.Host.Database.Design
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class MySqlDesignTimeDbContextFactory : IDesignTimeDbContextFactory<MySqlDatabaseContext>
|
||||
+2
-1
@@ -2,9 +2,10 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
namespace Tgstation.Server.Host.Database.Design
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class SqlServerDesignTimeDbContextFactory : IDesignTimeDbContextFactory<SqlServerDatabaseContext>
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
using System.Data.Common;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// For creating <see cref="DbConnection"/>
|
||||
/// For creating raw <see cref="DbConnection"/>s.
|
||||
/// </summary>
|
||||
interface IDBConnectionFactory
|
||||
interface IDatabaseConnectionFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a <see cref="DbConnection"/>
|
||||
+5
-4
@@ -2,8 +2,9 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the database
|
||||
@@ -51,7 +52,7 @@ namespace Tgstation.Server.Host.Models
|
||||
DbSet<DreamDaemonSettings> DreamDaemonSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.ChatBot"/> in the <see cref="IDatabaseContext"/>
|
||||
/// The <see cref="ChatBot"/>s in the <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
DbSet<ChatBot> ChatBots { get; set; }
|
||||
|
||||
@@ -66,12 +67,12 @@ namespace Tgstation.Server.Host.Models
|
||||
DbSet<RepositorySettings> RepositorySettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DbSet{TEntity}"/> for <see cref="Models.ReattachInformation"/>s
|
||||
/// The <see cref="DbSet{TEntity}"/> for <see cref="ReattachInformation"/>s
|
||||
/// </summary>
|
||||
DbSet<ReattachInformation> ReattachInformations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DbSet{TEntity}"/> for <see cref="Models.WatchdogReattachInformation"/>s
|
||||
/// The <see cref="DbSet{TEntity}"/> for <see cref="WatchdogReattachInformation"/>s
|
||||
/// </summary>
|
||||
DbSet<WatchdogReattachInformation> WatchdogReattachInformations { get; set; }
|
||||
|
||||
+1
-2
@@ -1,8 +1,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for scoping usage of <see cref="IDatabaseContext"/>s. Meant for use by <see cref="Components"/>
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// For initially seeding a database
|
||||
+1
-1
@@ -6,7 +6,7 @@ using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
|
||||
using System;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="DatabaseContext{TParentContext}"/> for MySQL
|
||||
+1
-1
@@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="DatabaseContext{TParentContext}"/> for Sqlserver
|
||||
+2
-2
@@ -9,7 +9,7 @@ using System.Globalization;
|
||||
using System.Net;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
namespace Tgstation.Server.Host.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for <see cref="IApplicationBuilder"/>
|
||||
@@ -21,7 +21,7 @@ namespace Tgstation.Server.Host.Core
|
||||
/// </summary>
|
||||
/// <param name="httpContext">The <see cref="HttpContext"/> to get the <see cref="ILogger"/> from</param>
|
||||
/// <returns>A new <see cref="ILogger"/></returns>
|
||||
static ILogger GetLogger(HttpContext httpContext) => httpContext.RequestServices.GetRequiredService<ILogger<Application>>();
|
||||
static ILogger GetLogger(HttpContext httpContext) => httpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(typeof(ApplicationBuilderExtensions));
|
||||
|
||||
/// <summary>
|
||||
/// Return a <see cref="ConflictObjectResult"/> for <see cref="DbUpdateException"/>s
|
||||
+1
-1
@@ -4,7 +4,7 @@ using System;
|
||||
using System.Globalization;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
namespace Tgstation.Server.Host.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for <see cref="IServiceCollection"/>
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
namespace Tgstation.Server.Host.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for the <see cref="Task"/> <see langword="class"/>.
|
||||
@@ -30,7 +30,7 @@ namespace Tgstation.Server.Host.IO
|
||||
public Console()
|
||||
{
|
||||
cancelKeyCts = new CancellationTokenSource();
|
||||
System.Console.CancelKeyPress += (sender, e) =>
|
||||
global::System.Console.CancelKeyPress += (sender, e) =>
|
||||
{
|
||||
lock (cancelKeyCts)
|
||||
{
|
||||
@@ -60,7 +60,7 @@ namespace Tgstation.Server.Host.IO
|
||||
public Task PressAnyKeyAsync(CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
{
|
||||
CheckAvailable();
|
||||
System.Console.Read();
|
||||
global::System.Console.Read();
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -69,12 +69,12 @@ namespace Tgstation.Server.Host.IO
|
||||
// TODO: Make this better: https://stackoverflow.com/questions/9479573/how-to-interrupt-console-readline
|
||||
CheckAvailable();
|
||||
if (!usePasswordChar)
|
||||
return System.Console.ReadLine();
|
||||
return global::System.Console.ReadLine();
|
||||
|
||||
var passwordBuilder = new StringBuilder();
|
||||
do
|
||||
{
|
||||
var keyDescription = System.Console.ReadKey(true);
|
||||
var keyDescription = global::System.Console.ReadKey(true);
|
||||
if (keyDescription.Key == ConsoleKey.Enter)
|
||||
break;
|
||||
else if (keyDescription.Key == ConsoleKey.Backspace)
|
||||
@@ -82,20 +82,20 @@ namespace Tgstation.Server.Host.IO
|
||||
if (passwordBuilder.Length > 0)
|
||||
{
|
||||
--passwordBuilder.Length;
|
||||
System.Console.Write("\b \b");
|
||||
global::System.Console.Write("\b \b");
|
||||
}
|
||||
}
|
||||
else if (keyDescription.KeyChar != '\u0000')
|
||||
{
|
||||
// KeyChar == '\u0000' if the key pressed does not correspond to a printable character, e.g. F1, Pause-Break, etc
|
||||
passwordBuilder.Append(keyDescription.KeyChar);
|
||||
System.Console.Write('*');
|
||||
global::System.Console.Write('*');
|
||||
}
|
||||
}
|
||||
while (!cancellationToken.IsCancellationRequested);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
System.Console.WriteLine();
|
||||
global::System.Console.WriteLine();
|
||||
return passwordBuilder.ToString();
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
@@ -107,12 +107,12 @@ namespace Tgstation.Server.Host.IO
|
||||
{
|
||||
if (!newLine)
|
||||
throw new InvalidOperationException("Cannot write null text without a new line!");
|
||||
System.Console.WriteLine();
|
||||
global::System.Console.WriteLine();
|
||||
}
|
||||
else if (newLine)
|
||||
System.Console.WriteLine(text);
|
||||
global::System.Console.WriteLine(text);
|
||||
else
|
||||
System.Console.Write(text);
|
||||
global::System.Console.Write(text);
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,15 +30,17 @@ namespace Tgstation.Server.Host.IO
|
||||
{
|
||||
var tasks = new List<Task>();
|
||||
|
||||
// check if we are a symbolic link
|
||||
if (!dir.Attributes.HasFlag(FileAttributes.Directory) || dir.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||
{
|
||||
dir.Delete();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var subDir in dir.EnumerateDirectories())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// if below succeeds this is probably a symlink
|
||||
if (!subDir.Attributes.HasFlag(FileAttributes.Directory) || subDir.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||
subDir.Delete();
|
||||
else
|
||||
tasks.Add(NormalizeAndDelete(subDir, cancellationToken));
|
||||
tasks.Add(NormalizeAndDelete(subDir, cancellationToken));
|
||||
}
|
||||
|
||||
foreach (var file in dir.EnumerateFiles())
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Threading.Tasks;
|
||||
namespace Tgstation.Server.Host.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstraction for <see cref="System.Console"/>
|
||||
/// Abstraction for <see cref="global::System.Console"/>
|
||||
/// </summary>
|
||||
interface IConsole
|
||||
{
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Tgstation.Server.Host.IO
|
||||
Task CreateDirectory(string path, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Recursively delete a directory
|
||||
/// Recursively delete a directory, removes and does not enter any symlinks encounterd.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the directory to delete</param>
|
||||
/// <param name="cancellationToken">A <see cref="CancellationToken"/> for the operation</param>
|
||||
@@ -183,7 +183,7 @@ namespace Tgstation.Server.Host.IO
|
||||
/// Extract a set of <paramref name="zipFileBytes"/> to a given <paramref name="path"/>
|
||||
/// </summary>
|
||||
/// <param name="path">The path to unzip to</param>
|
||||
/// <param name="zipFileBytes">The <see cref="byte"/>s of the <see cref="System.IO.Compression.ZipArchive"/></param>
|
||||
/// <param name="zipFileBytes">The <see cref="byte"/>s of the <see cref="global::System.IO.Compression.ZipArchive"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task ZipToDirectory(string path, byte[] zipFileBytes, CancellationToken cancellationToken);
|
||||
|
||||
+2
-1
@@ -2,9 +2,10 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
namespace Tgstation.Server.Host.Jobs
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the runtime of <see cref="Job"/>s
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
namespace Tgstation.Server.Host.Jobs
|
||||
{
|
||||
/// <summary>
|
||||
/// Operation exceptions thrown from the context of a <see cref="Models.Job"/>
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
namespace Tgstation.Server.Host.Jobs
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for pairing <see cref="Task"/>s with <see cref="CancellationTokenSource"/>s
|
||||
+2
-1
@@ -5,9 +5,10 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
namespace Tgstation.Server.Host.Jobs
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class JobManager : IJobManager, IDisposable
|
||||
+1
@@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Tgstation.Server.Host.Database;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
{
|
||||
|
||||
+1
@@ -3,6 +3,7 @@ using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Tgstation.Server.Host.Database;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
{
|
||||
|
||||
Generated
+1
@@ -3,6 +3,7 @@ using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Tgstation.Server.Host.Database;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
{
|
||||
|
||||
Generated
+1
@@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Tgstation.Server.Host.Database;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
{
|
||||
|
||||
+1
@@ -3,6 +3,7 @@ using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Tgstation.Server.Host.Database;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
{
|
||||
|
||||
+1
@@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Tgstation.Server.Host.Database;
|
||||
|
||||
namespace Tgstation.Server.Host.Models.Migrations
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user