diff --git a/.dockerignore b/.dockerignore
index 677b545415..4be960f56a 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -7,13 +7,13 @@
.travis.yml
.codecov.yml
appveyor.yml
-LICENSE
README
build/**
!build/analyzers.ruleset
!build/tgs.docker.sh
!build/tgs.ico
!build/stylecop.json
+!build/Version.props
*/bin
*/obj
tests
diff --git a/.travis.yml b/.travis.yml
index 94d49a8e0d..4a93935100 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,7 @@ git:
matrix:
include:
- env:
+ - DockerBuild=false
- DMAPI=true
- BYOND_MAJOR="511"
- BYOND_MINOR="1385"
@@ -21,34 +22,42 @@ matrix:
- libc6-i386
- libstdc++6:i386
- env:
+ - DockerBuild=false
- DMAPI=false
- CONFIG=Debug
name: "Test Server Debug"
language: csharp
mono: none
- dotnet: 2.1.300
+ dotnet: 2.2.207
services:
- mysql
cache:
directories:
- $HOME/.nuget/packages:
- env:
+ - DockerBuild=false
- DMAPI=false
- CONFIG=Release
name: "Test Server Release"
language: csharp
mono: none
- dotnet: 2.1.300
+ dotnet: 2.2.207
services:
- mysql
cache:
directories:
- $HOME/.nuget/packages:
+ - env:
+ - DockerBuild=true
+ name: "Docker Build"
+ services:
+ - docker
install:
- - if [ $DMAPI = true ]; then build/install_byond.sh; fi
- - if [ $DMAPI = false ]; then dotnet restore tgstation-server.sln; fi
+ - if [ $DockerBuild = false ] && [ $DMAPI = true ]; then build/install_byond.sh; fi
+ - if [ $DockerBuild = false ] && [ $DMAPI = false ]; then dotnet restore tgstation-server.sln; fi
script:
- - if [ $DMAPI = true ]; then tests/DMAPI/build_byond.sh || travis_terminate 1; fi
- - if [ $DMAPI = false ]; then build/test_core.sh; fi
+ - if [ $DockerBuild = false ] && [ $DMAPI = true ]; then tests/DMAPI/build_byond.sh || travis_terminate 1; fi
+ - if [ $DockerBuild = false ] && [ $DMAPI = false ]; then build/test_core.sh; fi
+ - if [ $DockerBuild = true ]; then docker build . -f build/Dockerfile; fi
diff --git a/appveyor.yml b/appveyor.yml
index d6362a9ef2..963fced591 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -8,11 +8,12 @@ environment:
secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK
TGS_RELEASE_NOTES_TOKEN:
secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK
+
branches:
only:
- master
skip_tags: true
-image: Visual Studio 2017
+image: Visual Studio 2019
configuration:
- Debug
- Release
@@ -35,7 +36,7 @@ cache:
services:
- mssql2017
install:
- - choco install doxygen.portable codecov graphviz.portable opencover.portable
+ - choco install doxygen.install codecov graphviz.portable opencover.portable
- nuget restore tgstation-server.sln
- ps: Install-Product node 10
build:
@@ -56,9 +57,8 @@ 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.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Console.Tests*]*" -output:".\console_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.Host.Console.Tests\TestResults\results.trx))
- - set path=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;%path%
- - copy "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions\appveyor.*" "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow\Extensions" /y
- - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Service.Tests\bin\%CONFIGURATION%\Tgstation.Server.Host.Service.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64
+ - set path=%ProgramFiles(x86)%\Microsoft Visual Studio\2019\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;%path%
+ - vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Service.Tests\bin\%CONFIGURATION%\net471\Tgstation.Server.Host.Service.Tests.dll" /inIsolation /Platform:x64
- ps: $wc = New-Object 'System.Net.WebClient'
- ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))
- 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.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Watchdog.Tests*]*" -output:".\watchdog_coverage.xml" -oldstyle
@@ -71,9 +71,9 @@ after_test:
- ps: build/UploadCoverage.ps1
- ps: build/BuildDox.ps1
#host updater
- - dotnet publish src/Tgstation.Server.Host/Tgstation.Server.Host.csproj -o ../../artifacts/ServerHost -c %CONFIGURATION%
+ - dotnet publish src/Tgstation.Server.Host/Tgstation.Server.Host.csproj -o artifacts/ServerHost -c %CONFIGURATION%
#console
- - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION%
+ - dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o artifacts/ServerConsole -c %CONFIGURATION%
- ps: Copy-Item -path "artifacts/ServerHost" -destination artifacts/ServerConsole/lib/Default -recurse
- ps: Move-Item -path artifacts/ServerConsole/lib/Default/appsettings.json -destination artifacts/ServerConsole/
#service
diff --git a/build/BuildDox.ps1 b/build/BuildDox.ps1
index 62288118ac..e41968ebb8 100644
--- a/build/BuildDox.ps1
+++ b/build/BuildDox.ps1
@@ -16,7 +16,7 @@ if($publish_dox){
Add-Content "$bf\docs\Doxyfile" "`nPROJECT_NUMBER = $version`nINPUT = $bf`nOUTPUT_DIRECTORY = $doxdir`nPROJECT_LOGO = $bf/build/tgs.ico`nHAVE_DOT=YES"
}
-doxygen.exe "$bf\docs\Doxyfile"
+&"C:\Program Files\doxygen\bin\doxygen.exe" "$bf\docs\Doxyfile"
if($publish_dox){
cd $doxdir
diff --git a/build/UploadCoverage.ps1 b/build/UploadCoverage.ps1
index 61a0bd1069..a74244feee 100644
--- a/build/UploadCoverage.ps1
+++ b/build/UploadCoverage.ps1
@@ -1,16 +1,7 @@
-$coverageFilePaths = Get-ChildItem -Path TestResults -Filter *.coverage -Recurse -ErrorAction SilentlyContinue -Force | %{ $_.fullname }
-
-$coverageFilePathList = [string]$coverageFilePaths
-
-Write-Host "Running CodeCoverage.exe..."
-&"C:\Program Files (x86)\Microsoft Visual Studio\2017\TestAgent\Team Tools\Dynamic Code Coverage Tools\CodeCoverage.exe" analyze /output:service.coveragexml "$coverageFilePathList"
-
-rm -r TestResults
-
codecov -f api_coverage.xml --flag unittests
codecov -f client_coverage.xml --flag unittests
codecov -f host_coverage.xml --flag unittests
codecov -f console_coverage.xml --flag unittests
codecov -f watchdog_coverage.xml --flag unittests
-codecov -f service.coveragexml --flag unittests
+#codecov -f service.coveragexml --flag unittests
codecov -f server_coverage.xml --flag integration
diff --git a/build/Version.props b/build/Version.props
new file mode 100644
index 0000000000..68a795b9da
--- /dev/null
+++ b/build/Version.props
@@ -0,0 +1,7 @@
+
+
+ 4.0.1.4
+ 4.0.2
+ 4.0.3.0
+
+
diff --git a/build/analyzers.ruleset b/build/analyzers.ruleset
index 75495a4ace..bbf7b12dab 100644
--- a/build/analyzers.ruleset
+++ b/build/analyzers.ruleset
@@ -30,7 +30,7 @@
-
+
@@ -66,7 +66,7 @@
-
+
diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs
index 6128de76be..b1be91ff6f 100644
--- a/src/Tgstation.Server.Api/ApiHeaders.cs
+++ b/src/Tgstation.Server.Api/ApiHeaders.cs
@@ -100,7 +100,7 @@ namespace Tgstation.Server.Api
///
/// The to test
/// if the given version is compatible with the API. otherwise
- public static bool CheckCompatibility(Version otherVersion) => !(Version.Major != otherVersion.Major || Version.Minor > otherVersion.Minor);
+ public static bool CheckCompatibility(Version otherVersion) => !(Version.Major != (otherVersion?.Major ?? throw new ArgumentNullException(nameof(otherVersion))) || Version.Minor > otherVersion.Minor);
///
/// Construct for JWT authentication
@@ -137,6 +137,9 @@ namespace Tgstation.Server.Api
/// The containing the
public ApiHeaders(RequestHeaders requestHeaders)
{
+ if (requestHeaders == null)
+ throw new ArgumentNullException(nameof(requestHeaders));
+
var jsonAccept = new Microsoft.Net.Http.Headers.MediaTypeHeaderValue(ApplicationJson);
if (!requestHeaders.Accept.Any(x => x.MediaType == jsonAccept.MediaType))
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Client does not accept {0}!", ApplicationJson));
diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatBot.cs b/src/Tgstation.Server.Api/Models/Internal/ChatBot.cs
index 577321132d..78434aebca 100644
--- a/src/Tgstation.Server.Api/Models/Internal/ChatBot.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/ChatBot.cs
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
namespace Tgstation.Server.Api.Models.Internal
{
@@ -37,31 +36,31 @@ namespace Tgstation.Server.Api.Models.Internal
public string ConnectionString { get; set; }
///
- /// The which maps to the
+ /// Get the which maps to the .
///
- [NotMapped]
- public ChatConnectionStringBuilder ConnectionStringBuilder
+ /// A for the .
+ public ChatConnectionStringBuilder CreateConnectionStringBuilder()
{
- get
+ if (ConnectionString == null)
+ return null;
+ switch (Provider)
{
- if (ConnectionString == null)
- return null;
- switch (Provider)
- {
- case ChatProvider.Discord:
- return new DiscordConnectionStringBuilder(ConnectionString);
- case ChatProvider.Irc:
- return new IrcConnectionStringBuilder(ConnectionString);
- default:
- throw new InvalidOperationException("Invalid Provider!");
- }
- }
- set
- {
- if (value?.Valid == false)
- throw new InvalidOperationException("Cannot set invalid ChatConnectionStringBuilder!");
- ConnectionString = value?.ToString();
+ case ChatProvider.Discord:
+ return new DiscordConnectionStringBuilder(ConnectionString);
+ case ChatProvider.Irc:
+ return new IrcConnectionStringBuilder(ConnectionString);
+ default:
+ throw new InvalidOperationException("Invalid Provider!");
}
}
+
+ ///
+ /// Set the for the . Also updates the .
+ ///
+ /// The optional .
+ public void SetConnectionStringBuilder(ChatConnectionStringBuilder stringBuilder)
+ {
+ ConnectionString = stringBuilder?.ToString();
+ }
}
}
diff --git a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs
index 34054f4487..43999fb783 100644
--- a/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/DreamDaemonLaunchParameters.cs
@@ -1,4 +1,5 @@
-using System.ComponentModel.DataAnnotations;
+using System;
+using System.ComponentModel.DataAnnotations;
namespace Tgstation.Server.Api.Models.Internal
{
@@ -43,7 +44,7 @@ namespace Tgstation.Server.Api.Models.Internal
/// The to compare against
/// if they match, otherwise
public bool Match(DreamDaemonLaunchParameters otherParameters) =>
- AllowWebClient == otherParameters.AllowWebClient
+ AllowWebClient == (otherParameters?.AllowWebClient ?? throw new ArgumentNullException(nameof(otherParameters)))
&& SecurityLevel == otherParameters.SecurityLevel
&& PrimaryPort == otherParameters.PrimaryPort
&& SecondaryPort == otherParameters.SecondaryPort
diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
index cc6aea401c..f052135a97 100644
--- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
+++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj
@@ -1,4 +1,5 @@
-
+
+
netstandard2.0
@@ -7,15 +8,15 @@
Cyberboss
/tg/station
API definitions for tgstation-server
- https://github.com/tgstation/tgstation-server/blob/master/LICENSE.md
+ LICENSE
+ tgs.ico
https://tgstation.github.io/tgstation-server
- https://raw.githubusercontent.com/tgstation/tgstation-server/master/build/tgs.ico
Git
https://github.com/tgstation/tgstation-server
2018
json web api tgstation-server tgstation ss13 byond
Added ApiHeaders.RawUserAgent
- 4.0.2.0
+ $(TgsApiVersion)
../../build/analyzers.ruleset
latest
@@ -33,19 +34,21 @@
-
-
+
+
all
- compile; build; native; contentfiles; analyzers
+ runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers
-
+
-
+
+
+
diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
index 647a6ebe46..594c5420a0 100644
--- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
+++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj
@@ -1,9 +1,10 @@
-
+
+
netstandard2.0
Full
- 4.0.3.0
+ $(TgsClientVersion)
true
Cyberboss
/tg/station 13
@@ -13,8 +14,8 @@
https://github.com/tgstation/tgstation-server
Git
en-CA
- https://raw.githubusercontent.com/tgstation/tgstation-server/master/build/tgs.ico
- https://github.com/tgstation/tgstation-server/blob/master/LICENSE
+ LICENSE
+ tgs.ico
json web api tgstation-server tgstation ss13 byond client
Added the ability to change the Token of IServerClient
2018
@@ -35,12 +36,8 @@
-
- all
- compile; build; native; contentfiles; analyzers
-
-
-
+
+
all
runtime; build; native; contentfiles; analyzers
@@ -52,5 +49,7 @@
+
+
diff --git a/src/Tgstation.Server.Host.Console/Program.cs b/src/Tgstation.Server.Host.Console/Program.cs
index 9437569d9f..54cdad151b 100644
--- a/src/Tgstation.Server.Host.Console/Program.cs
+++ b/src/Tgstation.Server.Host.Console/Program.cs
@@ -30,7 +30,9 @@ namespace Tgstation.Server.Host.Console
var trace = arguments.Remove("--trace-host-watchdog");
var debug = arguments.Remove("--debug-host-watchdog");
+#pragma warning disable CS0618 // Type or member is obsolete
loggerFactory.AddConsole(trace ? LogLevel.Trace : debug ? LogLevel.Debug : LogLevel.Information, true);
+#pragma warning restore CS0618 // Type or member is obsolete
if (trace && debug)
{
diff --git a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj
index c29a5d688d..86cbf78c30 100644
--- a/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj
+++ b/src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj
@@ -1,12 +1,14 @@
-
+
+
Exe
netcoreapp2.1
Full
- 4.0.1.4
+ $(TgsCoreVersion)
../../build/analyzers.ruleset
latest
+ false
@@ -21,12 +23,12 @@
-
+
all
compile; build; native; contentfiles; analyzers
-
-
+
+
all
runtime; build; native; contentfiles; analyzers
diff --git a/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs b/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs
index dfc5c7f3e3..40d2c0f847 100644
Binary files a/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs and b/src/Tgstation.Server.Host.Service/GlobalSuppressions.cs differ
diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs
index 6e519cc9d9..1ec0f9eae2 100644
--- a/src/Tgstation.Server.Host.Service/Program.cs
+++ b/src/Tgstation.Server.Host.Service/Program.cs
@@ -143,7 +143,8 @@ namespace Tgstation.Server.Host.Service
installer.Uninstall(null);
}
else if (!Configure)
- ServiceBase.Run(new ServerService(WatchdogFactory, loggerFactory, Trace ? LogLevel.Trace : Debug ? LogLevel.Debug : LogLevel.Information));
+ using (var service = new ServerService(WatchdogFactory, loggerFactory, Trace ? LogLevel.Trace : Debug ? LogLevel.Debug : LogLevel.Information))
+ ServiceBase.Run(service);
}
}
}
diff --git a/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs b/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs
index 2b77d018db..d7c3dd8a1b 100644
--- a/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs
+++ b/src/Tgstation.Server.Host.Service/Properties/AssemblyInfo.cs
@@ -1,11 +1,3 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
+using System.Runtime.CompilerServices;
-[assembly: AssemblyTitle("Tgstation.Server.Host.Service")]
-[assembly: ComVisible(false)]
-[assembly: Guid("29927416-3b78-49a7-a560-5ccaa638b6b4")]
-[assembly: InternalsVisibleTo("Tgstation.Server.Host.Service.Tests")]
-
-[assembly: AssemblyVersion("4.0.1.4")]
-[assembly: AssemblyFileVersion("4.0.1.4")]
+[assembly:InternalsVisibleTo("Tgstation.Server.Host.Service.Tests")]
diff --git a/src/Tgstation.Server.Host.Service/ServerService.cs b/src/Tgstation.Server.Host.Service/ServerService.cs
index a0ff1d7ae5..f94ed5fdcb 100644
--- a/src/Tgstation.Server.Host.Service/ServerService.cs
+++ b/src/Tgstation.Server.Host.Service/ServerService.cs
@@ -1,9 +1,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.EventLog;
-using Microsoft.Extensions.Logging.EventLog.Internal;
using System;
using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.ServiceProcess;
using System.Threading;
@@ -15,7 +13,7 @@ namespace Tgstation.Server.Host.Service
///
/// Represents a as a
///
- sealed class ServerService : ServiceBase, IEventLog
+ sealed class ServerService : ServiceBase
{
///
/// The canonical windows service name
@@ -50,22 +48,20 @@ namespace Tgstation.Server.Host.Service
if (loggerFactory == null)
throw new ArgumentNullException(nameof(loggerFactory));
+#pragma warning disable CS0618 // Type or member is obsolete
loggerFactory.AddEventLog(new EventLogSettings
{
- EventLog = this,
+ LogName = EventLog.Log,
+ MachineName = EventLog.MachineName,
+ SourceName = EventLog.Source,
Filter = (message, logLevel) => logLevel >= minumumLogLevel
});
+#pragma warning restore CS0618 // Type or member is obsolete
ServiceName = Name;
watchdog = watchdogFactory.CreateWatchdog(loggerFactory);
}
- ///
- public int MaxMessageSize => (int)EventLog.MaximumKilobytes * 1024;
-
- ///
- public void WriteEntry(string message, EventLogEntryType type, int eventID, short category) => EventLog.WriteEntry(message, type, eventID, category);
-
///
/// Executes the , stopping the service if it exits
///
@@ -74,7 +70,7 @@ namespace Tgstation.Server.Host.Service
/// A representing the running operation
async Task RunWatchdog(string[] args, CancellationToken cancellationToken)
{
- await watchdog.RunAsync(false, args, cancellationToken).ConfigureAwait(false);
+ await watchdog.RunAsync(false, args, cancellationTokenSource.Token).ConfigureAwait(false);
void StopServiceAsync()
{
@@ -93,7 +89,6 @@ namespace Tgstation.Server.Host.Service
}
///
- [SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "cancellationTokenSource", Justification = "IT'S DISPOSED RIGHT THERE YOU FUCCBOI!")]
protected override void Dispose(bool disposing)
{
cancellationTokenSource?.Dispose();
diff --git a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj
index 6bd52c6f69..479e6d6b87 100644
--- a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj
+++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj
@@ -1,116 +1,51 @@
-
-
-
+
+
+
- Debug
- AnyCPU
- {29927416-3B78-49A7-A560-5CCAA638B6B4}
- WinExe
- Tgstation.Server.Host.Service
- Tgstation.Server.Host.Service
- v4.7.1
- 512
- true
- publish\
- true
- Disk
- false
- Foreground
- 7
- Days
- false
- false
- true
- 0
- 1.0.0.%2a
- false
- false
- true
- latest
- 4
+ Exe
+ net471
+ win
+ Full
+ $(TgsCoreVersion)
../../build/analyzers.ruleset
- true
- AnyCPU
- prompt
-
+ latest
+ bin\Debug\Tgstation.Server.Host.Console.xml
+ false
-
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- bin\Debug\Tgstation.Server.Host.Service.xml
- SA1101;SA1121;SA1200;SA1202;SA1208;SA1400;SA1413;SA1501;SA1503;SA1519;SA1520;SA1623;SA1629;SA1633;SA1642
-
-
- pdbonly
- true
- bin\Release\
- TRACE
+
+
true
- bin\Release\Tgstation.Server.Host.Service.xml
- SA1101;SA1121;SA1200;SA1202;SA1208;SA1400;SA1413;SA1501;SA1503;SA1519;SA1520;SA1623;SA1629;SA1633;SA1642
+
-
- Tgstation.Server.Host.Service.Program
-
-
-
- ../../build/tgs.ico
+
+
+ 1701;1702;SA1652
+
-
-
-
-
- Component
-
-
-
-
- False
- Microsoft .NET Framework 4.7.1 %28x86 and x64%29
- true
-
-
- False
- .NET Framework 3.5 SP1
- false
-
-
-
-
-
-
-
-
-
-
- {5d2d682c-6bf0-439c-850b-6ab945bbeaea}
- Tgstation.Server.Host.Watchdog
-
-
-
-
- 2.2.5
-
-
+
+
all
- compile; build; native; contentfiles; analyzers
+ runtime; build; native; contentfiles; analyzers; buildtransitive
-
- 2.1.1
-
-
+
+
all
runtime; build; native; contentfiles; analyzers
+
-
- stylecop.json
-
+
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj
index 6bd413819f..b25f01246a 100644
--- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj
+++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj
@@ -1,12 +1,14 @@
+
netstandard2.0
Full
false
- 4.0.1.4
+ $(TgsCoreVersion)
../../build/analyzers.ruleset
latest
+ false
@@ -21,12 +23,12 @@
-
+
all
compile; build; native; contentfiles; analyzers
-
-
+
+
all
runtime; build; native; contentfiles; analyzers
diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs
index 9fce44bfbf..9f72e17de5 100644
--- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs
+++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs
@@ -29,6 +29,32 @@ namespace Tgstation.Server.Host.Watchdog
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
+ string GetDotnetPath(bool isWindows)
+ {
+ var enviromentPath = Environment.GetEnvironmentVariable("PATH");
+ var paths = enviromentPath.Split(';');
+
+ var exeName = "dotnet";
+ IEnumerable enumerator;
+ if (isWindows)
+ {
+ exeName += ".exe";
+ enumerator = paths;
+ }
+ else
+ enumerator = paths.Select(x => x.Split(':')).SelectMany(x => x);
+
+ enumerator = enumerator.Select(x => Path.Combine(x, exeName));
+
+ return enumerator
+ .Where(x =>
+ {
+ logger.LogTrace("Checking for dotnet at {0}", x);
+ return File.Exists(x);
+ })
+ .FirstOrDefault();
+ }
+
///
#pragma warning disable CA1502 // TODO: Decomplexify
public async Task RunAsync(bool runConfigure, string[] args, CancellationToken cancellationToken)
@@ -38,30 +64,8 @@ namespace Tgstation.Server.Host.Watchdog
string updateDirectory = null;
try
{
- var enviromentPath = Environment.GetEnvironmentVariable("PATH");
- var paths = enviromentPath.Split(';');
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
-
- var exeName = "dotnet";
- IEnumerable enumerator;
- if (isWindows)
- {
- exeName += ".exe";
- enumerator = paths;
- }
- else
- enumerator = paths.Select(x => x.Split(':')).SelectMany(x => x);
-
- enumerator = enumerator.Select(x => Path.Combine(x, exeName));
-
- var dotnetPath = enumerator
- .Where(x =>
- {
- logger.LogTrace("Checking for dotnet at {0}", x);
- return File.Exists(x);
- })
- .FirstOrDefault();
-
+ var dotnetPath = GetDotnetPath(isWindows);
if (dotnetPath == default)
{
logger.LogCritical("Unable to locate dotnet executable in PATH! Please ensure the .NET Core runtime is installed and is in your PATH!");
@@ -126,7 +130,7 @@ namespace Tgstation.Server.Host.Watchdog
'"' + updateDirectory + '"'
};
- if (Environment.GetCommandLineArgs().Any(x => x == "--attach-host-debugger"))
+ if (Environment.GetCommandLineArgs().Any(x => x.Equals("--attach-host-debugger", StringComparison.OrdinalIgnoreCase)))
arguments.Add("--attach-debugger");
if (runConfigure)
diff --git a/src/Tgstation.Server.Host/ClientApp/package.json b/src/Tgstation.Server.Host/ClientApp/package.json
index fc8ab6e698..407902f50f 100644
--- a/src/Tgstation.Server.Host/ClientApp/package.json
+++ b/src/Tgstation.Server.Host/ClientApp/package.json
@@ -5,7 +5,7 @@
"tgstation-server-control-panel": "0.1.6"
},
"scripts": {
- "build": "npm run clean && tslint -p tsconfig.json && react-scripts build && cp-cli node_modules/tgstation-server-control-panel/build/public/ build/ && cp-cli ../../../build/tgs.ico build/favicon.ico && minify-json build && rimraf ../wwwroot && move-cli build ../wwwroot",
+ "msbuild": "npm run clean && tslint -p tsconfig.json && react-scripts build && cp-cli node_modules/tgstation-server-control-panel/build/public/ build/ && cp-cli ../../../build/tgs.ico build/favicon.ico && minify-json build && rimraf ../wwwroot && move-cli build ../wwwroot",
"clean": "rimraf build"
},
"devDependencies": {
diff --git a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs
index 41646c7813..f551c3046f 100644
--- a/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/JsonTrackingContext.cs
@@ -66,6 +66,7 @@ namespace Tgstation.Server.Host.Components.Chat
public void Dispose()
{
logger.LogTrace("Disposing...");
+ channelsSemaphore.Dispose();
onDispose();
}
diff --git a/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs b/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs
index 14df6b306c..04a4874cfe 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ProviderFactory.cs
@@ -1,6 +1,5 @@
using Microsoft.Extensions.Logging;
using System;
-using System.Collections.Generic;
using System.Globalization;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Components.Chat.Providers;
@@ -44,7 +43,7 @@ namespace Tgstation.Server.Host.Components.Chat
{
if (settings == null)
throw new ArgumentNullException(nameof(settings));
- var builder = settings.ConnectionStringBuilder;
+ var builder = settings.CreateConnectionStringBuilder();
if (builder == null || !builder.Valid)
throw new InvalidOperationException("Invalid ChatConnectionStringBuilder!");
switch (settings.Provider)
diff --git a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs
index c077cab399..bd6b84fddd 100644
--- a/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Compiler/DmbFactory.cs
@@ -54,6 +54,11 @@ namespace Tgstation.Server.Host.Components.Compiler
///
readonly CancellationTokenSource cleanupCts;
+ ///
+ /// Map of s to locks on them.
+ ///
+ readonly IDictionary jobLockCounts;
+
///
/// representing calls to
///
@@ -69,8 +74,6 @@ namespace Tgstation.Server.Host.Components.Compiler
///
IDmbProvider nextDmbProvider;
- Dictionary jobLockCounts;
-
///
/// Construct a
///
diff --git a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs
index b4b52bf9ba..318243c064 100644
--- a/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs
+++ b/src/Tgstation.Server.Host/Components/Compiler/DreamMaker.cs
@@ -176,9 +176,9 @@ namespace Tgstation.Server.Host.Components.Compiler
var dirA = ioManager.ConcatPath(job.DirectoryName.ToString(), ADirectoryName);
job.MinimumSecurityLevel = securityLevel; // needed for the TempDmbProvider
- var provider = new TemporaryDmbProvider(ioManager.ResolvePath(dirA), String.Concat(job.DmeName, DmbExtension), job);
-
var timeoutAt = DateTimeOffset.Now.AddSeconds(timeout);
+
+ using (var provider = new TemporaryDmbProvider(ioManager.ResolvePath(dirA), String.Concat(job.DmeName, DmbExtension), job))
using (var controller = await sessionControllerFactory.LaunchNew(launchParameters, provider, byondLock, true, true, true, cancellationToken).ConfigureAwait(false))
{
var launchResult = await controller.LaunchResult.ConfigureAwait(false);
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs
index 997f463571..cfa7c81c0d 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionControllerFactory.cs
@@ -296,29 +296,30 @@ namespace Tgstation.Server.Host.Components.Watchdog
{
networkPromptReaper.RegisterProcess(process);
result = new SessionController(reattachInformation, process, byondLock, byondTopicSender, chatJsonTrackingContext, context, chat, loggerFactory.CreateLogger(), null, null);
+
+ process = null;
+ context = null;
+ byondLock = null;
+ chatJsonTrackingContext = null;
}
finally
{
- if (result == null)
- process.Dispose();
+ process?.Dispose();
}
}
finally
{
- if (result == null)
- context.Dispose();
+ context?.Dispose();
}
}
finally
{
- if (result == null)
- byondLock.Dispose();
+ byondLock?.Dispose();
}
}
finally
{
- if (result == null)
- chatJsonTrackingContext.Dispose();
+ chatJsonTrackingContext?.Dispose();
}
return result;
diff --git a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs
index 0aa84f2601..84ea3fb90d 100644
--- a/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs
+++ b/src/Tgstation.Server.Host/Components/Watchdog/Watchdog.cs
@@ -208,6 +208,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
DisposeAndNullControllers();
semaphore.Dispose();
restartRegistration.Dispose();
+
+ // mostly here to please fxcop, but it definitely should be disposed already
+ Debug.Assert(monitorCts == null, "We reached Disposes() an monitorCts is not null!");
+ monitorCts?.Dispose();
}
///
@@ -679,6 +683,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
monitorCts.Cancel();
await monitorTask.ConfigureAwait(false);
monitorCts.Dispose();
+ monitorCts = null;
monitorTask = null;
return true;
}
diff --git a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
index 605be61107..61198121f6 100644
--- a/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
+++ b/src/Tgstation.Server.Host/Controllers/AdministrationController.cs
@@ -98,6 +98,58 @@ namespace Tgstation.Server.Host.Controllers
return StatusCode(429);
}
+ ///
+ /// Try to download and apply an update with a given .
+ ///
+ /// The version of the server to update to.
+ /// The for the operation.
+ /// A resulting in the of the operation.
+ async Task CheckReleasesAndApplyUpdate(Version newVersion, CancellationToken cancellationToken)
+ {
+ Logger.LogDebug("Looking for GitHub releases version {0}...", newVersion);
+ IEnumerable releases;
+ try
+ {
+ var gitHubClient = GetGitHubClient();
+ releases = await gitHubClient
+ .Repository
+ .Release
+ .GetAll(updatesConfiguration.GitHubRepositoryId)
+ .WithToken(cancellationToken)
+ .ConfigureAwait(false);
+ }
+ catch (RateLimitExceededException e)
+ {
+ return RateLimit(e);
+ }
+ catch (ApiException e)
+ {
+ Logger.LogWarning(OctokitException, e);
+ return StatusCode((int)HttpStatusCode.FailedDependency);
+ }
+
+ releases = releases.Where(x => x.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture));
+
+ Logger.LogTrace("Release query complete!");
+
+ foreach (var release in releases)
+ if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version) && version == newVersion)
+ {
+ var asset = release.Assets.Where(x => x.Name.Equals(updatesConfiguration.UpdatePackageAssetName, StringComparison.Ordinal)).FirstOrDefault();
+ if (asset == default)
+ continue;
+
+ if (!serverUpdater.ApplyUpdate(version, new Uri(asset.BrowserDownloadUrl), ioManager))
+ return Conflict(new ErrorMessage
+ {
+ Message = "An update operation is already in progress!"
+ });
+ return Accepted(); // gtfo of here before all the cancellation tokens fire
+ }
+
+ return StatusCode((int)HttpStatusCode.Gone);
+ }
+
IGitHubClient GetGitHubClient() => String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken) ? gitHubClientFactory.CreateClient() : gitHubClientFactory.CreateClient(generalConfiguration.GitHubAccessToken);
///
@@ -163,41 +215,7 @@ namespace Tgstation.Server.Host.Controllers
Message = RestartNotSupportedException
});
- Logger.LogDebug("Looking for GitHub releases version {0}...", model.NewVersion);
- IEnumerable releases;
- try
- {
- var gitHubClient = GetGitHubClient();
- releases = (await gitHubClient.Repository.Release.GetAll(updatesConfiguration.GitHubRepositoryId).ConfigureAwait(false)).Where(x => x.TagName.StartsWith(updatesConfiguration.GitTagPrefix, StringComparison.InvariantCulture));
- cancellationToken.ThrowIfCancellationRequested();
- }
- catch (RateLimitExceededException e)
- {
- return RateLimit(e);
- }
- catch (ApiException e)
- {
- Logger.LogWarning(OctokitException, e);
- return StatusCode((int)HttpStatusCode.FailedDependency);
- }
-
- Logger.LogTrace("Release query complete!");
- foreach (var release in releases)
- if (Version.TryParse(release.TagName.Replace(updatesConfiguration.GitTagPrefix, String.Empty, StringComparison.Ordinal), out var version) && version == model.NewVersion)
- {
- var asset = release.Assets.Where(x => x.Name == updatesConfiguration.UpdatePackageAssetName).FirstOrDefault();
- if (asset == default)
- continue;
-
- if (!serverUpdater.ApplyUpdate(version, new Uri(asset.BrowserDownloadUrl), ioManager))
- return Conflict(new ErrorMessage
- {
- Message = "An update operation is already in progress!"
- });
- return Accepted(); // gtfo of here before all the cancellation tokens fire
- }
-
- return StatusCode((int)HttpStatusCode.Gone);
+ return await CheckReleasesAndApplyUpdate(model.NewVersion, cancellationToken).ConfigureAwait(false);
}
///
diff --git a/src/Tgstation.Server.Host/Controllers/ApiController.cs b/src/Tgstation.Server.Host/Controllers/ApiController.cs
index f937018e41..9565072343 100644
--- a/src/Tgstation.Server.Host/Controllers/ApiController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ApiController.cs
@@ -18,7 +18,6 @@ namespace Tgstation.Server.Host.Controllers
/// A for API functions
///
[Produces(ApiHeaders.ApplicationJson)]
- [Consumes(ApiHeaders.ApplicationJson)]
public abstract class ApiController : Controller
{
///
diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs
index 1c31b949b4..c45b51dac7 100644
--- a/src/Tgstation.Server.Host/Controllers/HomeController.cs
+++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs
@@ -123,24 +123,24 @@ namespace Tgstation.Server.Host.Controllers
if (ApiHeaders.IsTokenAuthentication)
return BadRequest(new Api.Models.ErrorMessage { Message = "Cannot create a token using another token!" });
- ISystemIdentity identity;
+ ISystemIdentity systemIdentity;
try
{
// trust the system over the database because a user's name can change while still having the same SID
- identity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false);
+ systemIdentity = await systemIdentityFactory.CreateSystemIdentity(ApiHeaders.Username, ApiHeaders.Password, cancellationToken).ConfigureAwait(false);
}
catch (NotImplementedException)
{
- identity = null;
+ systemIdentity = null;
}
- using (identity)
+ using (systemIdentity)
{
IQueryable query;
- if (identity == null)
+ if (systemIdentity == null)
query = DatabaseContext.Users.Where(x => x.CanonicalName == ApiHeaders.Username.ToUpperInvariant());
else
- query = DatabaseContext.Users.Where(x => x.SystemIdentifier == identity.Uid);
+ query = DatabaseContext.Users.Where(x => x.SystemIdentifier == systemIdentity.Uid);
var user = await query.Select(x => new User
{
Id = x.Id,
@@ -152,13 +152,14 @@ namespace Tgstation.Server.Host.Controllers
if (user == null)
return Unauthorized();
- if (identity == null)
+ if (systemIdentity == null)
{
var originalHash = user.PasswordHash;
if (!cryptographySuite.CheckUserPassword(user, ApiHeaders.Password))
return Unauthorized();
if (user.PasswordHash != originalHash)
{
+ Logger.LogDebug("User ID {0}'s password hash needs a refresh, updating database.", user.Id);
var updatedUser = new User
{
Id = user.Id
@@ -170,25 +171,27 @@ namespace Tgstation.Server.Host.Controllers
}
// check if the name changed and updoot accordingly
- else if (identity.Username != user.Name)
+ else if (systemIdentity.Username != user.Name)
{
+ Logger.LogDebug("User ID {0}'s system identity needs a refresh, updating database.", user.Id);
DatabaseContext.Users.Attach(user);
- user.Name = identity.Username;
+ user.Name = systemIdentity.Username;
user.CanonicalName = user.Name.ToUpperInvariant();
await DatabaseContext.Save(cancellationToken).ConfigureAwait(false);
}
+ // Now that the bookeeping is done, tell them to fuck off if necessary
if (!user.Enabled.Value)
return Forbid();
var token = await tokenFactory.CreateToken(user, cancellationToken).ConfigureAwait(false);
- if (identity != null)
+ if (systemIdentity != null)
{
// expire the identity slightly after the auth token in case of lag
var identExpiry = token.ExpiresAt.Value;
identExpiry += tokenFactory.ValidationParameters.ClockSkew;
identExpiry += TimeSpan.FromSeconds(15);
- identityCache.CacheSystemIdentity(user, identity, identExpiry);
+ identityCache.CacheSystemIdentity(user, systemIdentity, identExpiry);
}
Logger.LogDebug("Successfully logged in user {0}!", user.Id);
diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs
index fa37e32823..4f5438ca5f 100644
--- a/src/Tgstation.Server.Host/Core/Application.cs
+++ b/src/Tgstation.Server.Host/Core/Application.cs
@@ -104,6 +104,9 @@ namespace Tgstation.Server.Host.Core
// enable options which give us config reloading
services.AddOptions();
+ // this is needful for the setup wizard
+ services.AddLogging();
+
// other stuff needed for for setup wizard and configuration
services.AddSingleton();
services.AddSingleton();
@@ -231,9 +234,8 @@ namespace Tgstation.Server.Host.Core
// enable browser detection
services.AddDetectionCore().AddBrowser();
- // enable CORS if necessary
- if (controlPanelConfiguration.AllowAnyOrigin || controlPanelConfiguration.AllowedOrigins?.Count > 0)
- services.AddCors();
+ // CORS conditionally enabled later
+ services.AddCors();
void AddTypedContext() where TContext : DatabaseContext
{
@@ -369,12 +371,13 @@ namespace Tgstation.Server.Host.Core
corsBuilder = builder => builder.WithOrigins(controlPanelConfiguration.AllowedOrigins.ToArray());
}
- if (corsBuilder != null)
+ var originalBuilder = corsBuilder;
+ corsBuilder = builder =>
{
- var originalBuilder = corsBuilder;
- corsBuilder = builder => originalBuilder(builder.AllowAnyHeader().AllowAnyMethod());
- applicationBuilder.UseCors(corsBuilder);
- }
+ builder.AllowAnyHeader().AllowAnyMethod();
+ originalBuilder?.Invoke(builder);
+ };
+ applicationBuilder.UseCors(corsBuilder);
// Do not service requests until Ready is called, this will return 503 until that point
applicationBuilder.UseAsyncInitialization(async cancellationToken =>
diff --git a/src/Tgstation.Server.Host/Core/SetupWizard.cs b/src/Tgstation.Server.Host/Core/SetupWizard.cs
index 44fc7eb3ff..b7ef8aa1db 100644
--- a/src/Tgstation.Server.Host/Core/SetupWizard.cs
+++ b/src/Tgstation.Server.Host/Core/SetupWizard.cs
@@ -274,7 +274,10 @@ namespace Tgstation.Server.Host.Core
await console.WriteAsync("Testing create DB permission...", true, cancellationToken).ConfigureAwait(false);
using (var command = testConnection.CreateCommand())
{
- command.CommandText = String.Format(CultureInfo.InvariantCulture, "CREATE DATABASE {0}", databaseName);
+ // I really don't care about user sanitization here, they want to fuck their own DB? so be it
+#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
+ command.CommandText = $"CREATE DATABASE {databaseName}";
+#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}
@@ -282,7 +285,9 @@ namespace Tgstation.Server.Host.Core
await console.WriteAsync("Dropping test database...", true, cancellationToken).ConfigureAwait(false);
using (var command = testConnection.CreateCommand())
{
- command.CommandText = String.Format(CultureInfo.InvariantCulture, "DROP DATABASE {0}", databaseName);
+#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
+ command.CommandText = $"DROP DATABASE {databaseName}";
+#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
try
{
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
diff --git a/src/Tgstation.Server.Host/Core/TaskExtensions.cs b/src/Tgstation.Server.Host/Core/TaskExtensions.cs
new file mode 100644
index 0000000000..fb1b825313
--- /dev/null
+++ b/src/Tgstation.Server.Host/Core/TaskExtensions.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Tgstation.Server.Host.Core
+{
+ ///
+ /// Extensions for the .
+ ///
+ static class TaskExtensions
+ {
+ ///
+ /// Create a that can be awaited while respecting a given .
+ ///
+ /// The to add cancel support to.
+ /// The for the operation.
+ /// A representing the running operation.
+ public static Task WithToken(this Task task, CancellationToken cancellationToken)
+ {
+ if (task == null)
+ throw new ArgumentNullException(nameof(task));
+
+ async Task