mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-07-14 17:43:19 +01:00
WIP Stylecopping Host
This commit is contained in:
+6
-147
@@ -16,7 +16,6 @@
|
||||
<Rule Id="CA1009" Action="Warning" />
|
||||
<Rule Id="CA1010" Action="Warning" />
|
||||
<Rule Id="CA1011" Action="Warning" />
|
||||
<Rule Id="CA1012" Action="Warning" />
|
||||
<Rule Id="CA1013" Action="Warning" />
|
||||
<Rule Id="CA1016" Action="Warning" />
|
||||
<Rule Id="CA1017" Action="Warning" />
|
||||
@@ -744,10 +743,10 @@
|
||||
<Rule Id="IDE0042WithoutSuggestion" Action="Warning" />
|
||||
<Rule Id="IDE0044" Action="Warning" />
|
||||
<Rule Id="IDE0044WithoutSuggestion" Action="Warning" />
|
||||
<Rule Id="IDE0045" Action="Warning" />
|
||||
<Rule Id="IDE0045WithoutSuggestion" Action="Warning" />
|
||||
<Rule Id="IDE0046" Action="Warning" />
|
||||
<Rule Id="IDE0046WithoutSuggestion" Action="Warning" />
|
||||
<Rule Id="IDE0045" Action="None" />
|
||||
<Rule Id="IDE0045WithoutSuggestion" Action="None" />
|
||||
<Rule Id="IDE0046" Action="None" />
|
||||
<Rule Id="IDE0046WithoutSuggestion" Action="None" />
|
||||
<Rule Id="IDE0047" Action="Warning" />
|
||||
<Rule Id="IDE0047WithoutSuggestion" Action="Warning" />
|
||||
<Rule Id="IDE0048" Action="Warning" />
|
||||
@@ -968,7 +967,6 @@
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Microsoft.CodeQuality.Analyzers" RuleNamespace="Microsoft.CodeQuality.Analyzers">
|
||||
<Rule Id="CA1008" Action="Warning" />
|
||||
<Rule Id="CA1012" Action="Warning" />
|
||||
<Rule Id="CA1017" Action="Warning" />
|
||||
<Rule Id="CA1024" Action="Warning" />
|
||||
<Rule Id="CA1027" Action="Warning" />
|
||||
@@ -1028,168 +1026,29 @@
|
||||
</Rules>
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
|
||||
<Rule Id="SA1000" Action="None" />
|
||||
<Rule Id="SA1001" Action="Warning" />
|
||||
<Rule Id="SA1002" Action="Warning" />
|
||||
<Rule Id="SA1003" Action="Warning" />
|
||||
<Rule Id="SA1004" Action="Warning" />
|
||||
<Rule Id="SA1005" Action="Warning" />
|
||||
<Rule Id="SA1006" Action="Warning" />
|
||||
<Rule Id="SA1007" Action="Warning" />
|
||||
<Rule Id="SA1008" Action="Warning" />
|
||||
<Rule Id="SA1009" Action="Warning" />
|
||||
<Rule Id="SA1010" Action="Warning" />
|
||||
<Rule Id="SA1011" Action="Warning" />
|
||||
<Rule Id="SA1012" Action="Warning" />
|
||||
<Rule Id="SA1013" Action="Warning" />
|
||||
<Rule Id="SA1014" Action="Warning" />
|
||||
<Rule Id="SA1015" Action="Warning" />
|
||||
<Rule Id="SA1016" Action="Warning" />
|
||||
<Rule Id="SA1017" Action="Warning" />
|
||||
<Rule Id="SA1018" Action="Warning" />
|
||||
<Rule Id="SA1019" Action="Warning" />
|
||||
<Rule Id="SA1020" Action="Warning" />
|
||||
<Rule Id="SA1021" Action="Warning" />
|
||||
<Rule Id="SA1022" Action="Warning" />
|
||||
<Rule Id="SA1023" Action="Warning" />
|
||||
<Rule Id="SA1024" Action="Warning" />
|
||||
<Rule Id="SA1025" Action="Warning" />
|
||||
<Rule Id="SA1026" Action="Warning" />
|
||||
<Rule Id="SA1027" Action="None" />
|
||||
<Rule Id="SA1028" Action="Warning" />
|
||||
<Rule Id="SA1100" Action="Warning" />
|
||||
<Rule Id="SA1101" Action="None" />
|
||||
<Rule Id="SA1102" Action="Warning" />
|
||||
<Rule Id="SA1103" Action="Warning" />
|
||||
<Rule Id="SA1104" Action="Warning" />
|
||||
<Rule Id="SA1105" Action="Warning" />
|
||||
<Rule Id="SA1106" Action="Warning" />
|
||||
<Rule Id="SA1107" Action="Warning" />
|
||||
<Rule Id="SA1108" Action="Warning" />
|
||||
<Rule Id="SA1110" Action="Warning" />
|
||||
<Rule Id="SA1111" Action="Warning" />
|
||||
<Rule Id="SA1112" Action="Warning" />
|
||||
<Rule Id="SA1113" Action="Warning" />
|
||||
<Rule Id="SA1114" Action="Warning" />
|
||||
<Rule Id="SA1115" Action="Warning" />
|
||||
<Rule Id="SA1116" Action="None" />
|
||||
<Rule Id="SA1117" Action="Warning" />
|
||||
<Rule Id="SA1118" Action="Warning" />
|
||||
<Rule Id="SA1119" Action="Warning" />
|
||||
<Rule Id="SA1120" Action="Warning" />
|
||||
<Rule Id="SA1117" Action="None" />
|
||||
<Rule Id="SA1118" Action="None" />
|
||||
<Rule Id="SA1121" Action="None" />
|
||||
<Rule Id="SA1122" Action="Warning" />
|
||||
<Rule Id="SA1123" Action="Warning" />
|
||||
<Rule Id="SA1124" Action="Warning" />
|
||||
<Rule Id="SA1125" Action="Warning" />
|
||||
<Rule Id="SA1127" Action="None" />
|
||||
<Rule Id="SA1128" Action="None" />
|
||||
<Rule Id="SA1129" Action="Warning" />
|
||||
<Rule Id="SA1130" Action="Warning" />
|
||||
<Rule Id="SA1131" Action="Warning" />
|
||||
<Rule Id="SA1132" Action="Warning" />
|
||||
<Rule Id="SA1133" Action="Warning" />
|
||||
<Rule Id="SA1134" Action="Warning" />
|
||||
<Rule Id="SA1200" Action="None" />
|
||||
<Rule Id="SA1201" Action="None" />
|
||||
<Rule Id="SA1202" Action="None" />
|
||||
<Rule Id="SA1203" Action="Warning" />
|
||||
<Rule Id="SA1204" Action="Warning" />
|
||||
<Rule Id="SA1205" Action="Warning" />
|
||||
<Rule Id="SA1206" Action="Warning" />
|
||||
<Rule Id="SA1207" Action="Warning" />
|
||||
<Rule Id="SA1208" Action="None" />
|
||||
<Rule Id="SA1209" Action="Warning" />
|
||||
<Rule Id="SA1210" Action="Warning" />
|
||||
<Rule Id="SA1211" Action="Warning" />
|
||||
<Rule Id="SA1212" Action="Warning" />
|
||||
<Rule Id="SA1213" Action="Warning" />
|
||||
<Rule Id="SA1214" Action="Warning" />
|
||||
<Rule Id="SA1216" Action="Warning" />
|
||||
<Rule Id="SA1217" Action="Warning" />
|
||||
<Rule Id="SA1300" Action="Warning" />
|
||||
<Rule Id="SA1302" Action="Warning" />
|
||||
<Rule Id="SA1303" Action="Warning" />
|
||||
<Rule Id="SA1304" Action="Warning" />
|
||||
<Rule Id="SA1306" Action="Warning" />
|
||||
<Rule Id="SA1307" Action="Warning" />
|
||||
<Rule Id="SA1308" Action="Warning" />
|
||||
<Rule Id="SA1309" Action="Warning" />
|
||||
<Rule Id="SA1310" Action="Warning" />
|
||||
<Rule Id="SA1311" Action="Warning" />
|
||||
<Rule Id="SA1312" Action="None" />
|
||||
<Rule Id="SA1313" Action="Warning" />
|
||||
<Rule Id="SA1400" Action="None" />
|
||||
<Rule Id="SA1401" Action="Warning" />
|
||||
<Rule Id="SA1402" Action="Warning" />
|
||||
<Rule Id="SA1403" Action="Warning" />
|
||||
<Rule Id="SA1404" Action="Warning" />
|
||||
<Rule Id="SA1405" Action="Warning" />
|
||||
<Rule Id="SA1406" Action="Warning" />
|
||||
<Rule Id="SA1407" Action="Warning" />
|
||||
<Rule Id="SA1408" Action="Warning" />
|
||||
<Rule Id="SA1410" Action="Warning" />
|
||||
<Rule Id="SA1411" Action="Warning" />
|
||||
<Rule Id="SA1500" Action="Warning" />
|
||||
<Rule Id="SA1501" Action="None" />
|
||||
<Rule Id="SA1502" Action="None" />
|
||||
<Rule Id="SA1503" Action="None" />
|
||||
<Rule Id="SA1504" Action="Warning" />
|
||||
<Rule Id="SA1505" Action="Warning" />
|
||||
<Rule Id="SA1506" Action="Warning" />
|
||||
<Rule Id="SA1507" Action="Warning" />
|
||||
<Rule Id="SA1508" Action="Warning" />
|
||||
<Rule Id="SA1509" Action="Warning" />
|
||||
<Rule Id="SA1510" Action="Warning" />
|
||||
<Rule Id="SA1511" Action="Warning" />
|
||||
<Rule Id="SA1512" Action="Warning" />
|
||||
<Rule Id="SA1513" Action="Warning" />
|
||||
<Rule Id="SA1514" Action="Warning" />
|
||||
<Rule Id="SA1515" Action="Warning" />
|
||||
<Rule Id="SA1516" Action="None" />
|
||||
<Rule Id="SA1517" Action="Warning" />
|
||||
<Rule Id="SA1518" Action="Warning" />
|
||||
<Rule Id="SA1519" Action="None" />
|
||||
<Rule Id="SA1520" Action="None" />
|
||||
<Rule Id="SA1600" Action="Warning" />
|
||||
<Rule Id="SA1601" Action="Warning" />
|
||||
<Rule Id="SA1602" Action="Warning" />
|
||||
<Rule Id="SA1604" Action="Warning" />
|
||||
<Rule Id="SA1605" Action="Warning" />
|
||||
<Rule Id="SA1606" Action="Warning" />
|
||||
<Rule Id="SA1607" Action="Warning" />
|
||||
<Rule Id="SA1608" Action="Warning" />
|
||||
<Rule Id="SA1610" Action="Warning" />
|
||||
<Rule Id="SA1611" Action="Warning" />
|
||||
<Rule Id="SA1612" Action="Warning" />
|
||||
<Rule Id="SA1613" Action="Warning" />
|
||||
<Rule Id="SA1614" Action="Warning" />
|
||||
<Rule Id="SA1615" Action="Warning" />
|
||||
<Rule Id="SA1616" Action="Warning" />
|
||||
<Rule Id="SA1617" Action="Warning" />
|
||||
<Rule Id="SA1618" Action="Warning" />
|
||||
<Rule Id="SA1619" Action="Warning" />
|
||||
<Rule Id="SA1620" Action="Warning" />
|
||||
<Rule Id="SA1621" Action="Warning" />
|
||||
<Rule Id="SA1622" Action="Warning" />
|
||||
<Rule Id="SA1623" Action="None" />
|
||||
<Rule Id="SA1624" Action="Warning" />
|
||||
<Rule Id="SA1625" Action="Warning" />
|
||||
<Rule Id="SA1626" Action="Warning" />
|
||||
<Rule Id="SA1627" Action="Warning" />
|
||||
<Rule Id="SA1633" Action="None" />
|
||||
<Rule Id="SA1634" Action="Warning" />
|
||||
<Rule Id="SA1635" Action="Warning" />
|
||||
<Rule Id="SA1636" Action="Warning" />
|
||||
<Rule Id="SA1637" Action="Warning" />
|
||||
<Rule Id="SA1638" Action="Warning" />
|
||||
<Rule Id="SA1640" Action="Warning" />
|
||||
<Rule Id="SA1641" Action="Warning" />
|
||||
<Rule Id="SA1642" Action="None" />
|
||||
<Rule Id="SA1643" Action="Warning" />
|
||||
<Rule Id="SA1648" Action="Warning" />
|
||||
<Rule Id="SA1649" Action="Warning" />
|
||||
<Rule Id="SA1651" Action="Warning" />
|
||||
<Rule Id="SA1652" Action="Warning" />
|
||||
</Rules>
|
||||
<Rules AnalyzerId="Text.CSharp.Analyzers" RuleNamespace="Text.CSharp.Analyzers">
|
||||
<Rule Id="CA1704" Action="Warning" />
|
||||
|
||||
@@ -27,8 +27,8 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
DreamMakerPath = dreamMakerPath ?? throw new ArgumentNullException(nameof(dreamMakerPath));
|
||||
}
|
||||
|
||||
//at one point in design, byond versions were to delete themselves if they werent the active version
|
||||
//That changed at some point so these functions are intentioanlly left blank
|
||||
// at one point in design, byond versions were to delete themselves if they weren't the active version
|
||||
// That changed at some point so these functions are intentioanlly left blank
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() { }
|
||||
|
||||
@@ -24,6 +24,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// The file in which we store the <see cref="VersionKey(Version)"/> for installations
|
||||
/// </summary>
|
||||
const string VersionFileName = "Version.txt";
|
||||
|
||||
/// <summary>
|
||||
/// The file in which we store the <see cref="VersionKey(Version)"/> for the active installation
|
||||
/// </summary>
|
||||
@@ -119,6 +120,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
if (!installed)
|
||||
installedVersions.Add(versionKey, ourTcs.Task);
|
||||
}
|
||||
|
||||
if (installed)
|
||||
using (cancellationToken.Register(() => ourTcs.SetCanceled()))
|
||||
{
|
||||
@@ -126,7 +128,8 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
return;
|
||||
}
|
||||
//okay up to us to install it then
|
||||
|
||||
// okay up to us to install it then
|
||||
try
|
||||
{
|
||||
await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List<string> { versionKey }, cancellationToken).ConfigureAwait(false);
|
||||
@@ -141,12 +144,12 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
await ioManager.ZipToDirectory(versionKey, download, cancellationToken).ConfigureAwait(false);
|
||||
await byondInstaller.InstallByond(ioManager.ResolvePath(versionKey), version, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//make sure to do this last because this is what tells us we have a valid version in the future
|
||||
// make sure to do this last because this is what tells us we have a valid version in the future
|
||||
await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
//since the user can easily provide non-exitent version numbers, we'll turn this into a JobException
|
||||
// since the user can easily provide non-exitent version numbers, we'll turn this into a JobException
|
||||
throw new JobException(String.Format(CultureInfo.InvariantCulture, "Error downloading BYOND version: {0}", e.Message));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -205,9 +208,8 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
{
|
||||
async Task<byte[]> GetActiveVersion()
|
||||
{
|
||||
if (!await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(false))
|
||||
return null;
|
||||
return await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(false);
|
||||
var activeVersionFileExists = await ioManager.FileExists(ActiveVersionFileName, cancellationToken).ConfigureAwait(false);
|
||||
return !activeVersionFileExists ? null : await ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var activeVersionBytesTask = GetActiveVersion();
|
||||
@@ -221,9 +223,10 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
if (!await ioManager.FileExists(versionFile, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
logger.LogInformation("Cleaning unparsable version path: {0}", ioManager.ResolvePath(path));
|
||||
await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false); //cleanup
|
||||
await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false); // cleanup
|
||||
return;
|
||||
}
|
||||
|
||||
var bytes = await ioManager.ReadAllBytes(versionFile, cancellationToken).ConfigureAwait(false);
|
||||
var text = Encoding.UTF8.GetString(bytes);
|
||||
if (Version.TryParse(text, out var version))
|
||||
@@ -236,8 +239,9 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false);
|
||||
};
|
||||
}
|
||||
|
||||
await Task.WhenAll(directories.Select(x => ReadVersion(x))).ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Byond
|
||||
@@ -18,7 +16,8 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// <summary>
|
||||
/// The URL format string for getting BYOND linux version {0}.{1} zipfile
|
||||
/// </summary>
|
||||
const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip";
|
||||
const string ByondRevisionsURLTemplate = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip";
|
||||
|
||||
/// <summary>
|
||||
/// Path to the BYOND cache
|
||||
/// </summary>
|
||||
@@ -85,7 +84,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
if (version == null)
|
||||
throw new ArgumentNullException(nameof(version));
|
||||
|
||||
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Minor);
|
||||
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURLTemplate, version.Major, version.Minor);
|
||||
|
||||
return await ioManager.DownloadFile(new Uri(url), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -98,8 +97,8 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
if (version == null)
|
||||
throw new ArgumentNullException(nameof(version));
|
||||
|
||||
//write the scripts for running the ting
|
||||
//need to add $ORIGIN to LD_LIBRARY_PATH
|
||||
// write the scripts for running the ting
|
||||
// need to add $ORIGIN to LD_LIBRARY_PATH
|
||||
const string StandardScript = "#!/bin/sh\nexport LD_LIBRARY_PATH=\"\\$ORIGIN:$LD_LIBRARY_PATH\"\nBASEDIR=$(dirname \"$0\")\nexec \"$BASEDIR/{0}\" \"$@\"\n";
|
||||
|
||||
var dreamDaemonScript = String.Format(CultureInfo.InvariantCulture, StandardScript, DreamDaemonExecutableName);
|
||||
|
||||
@@ -17,19 +17,23 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// <summary>
|
||||
/// The URL format string for getting BYOND windows version {0}.{1} zipfile
|
||||
/// </summary>
|
||||
const string ByondRevisionsURL = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip";
|
||||
const string ByondRevisionsURLTemplate = "https://secure.byond.com/download/build/{0}/{0}.{1}_byond.zip";
|
||||
|
||||
/// <summary>
|
||||
/// Directory to byond installation configuration
|
||||
/// </summary>
|
||||
const string ByondConfigDir = "byond/cfg";
|
||||
|
||||
/// <summary>
|
||||
/// BYOND's DreamDaemon config file
|
||||
/// </summary>
|
||||
const string ByondDDConfig = "daemon.txt";
|
||||
|
||||
/// <summary>
|
||||
/// Setting to add to <see cref="ByondDDConfig"/> to suppress an invisible user prompt for running a trusted mode .dmb
|
||||
/// </summary>
|
||||
const string ByondNoPromptTrustedMode = "trusted-check 0";
|
||||
|
||||
/// <summary>
|
||||
/// The directory that contains the BYOND directx redistributable
|
||||
/// </summary>
|
||||
@@ -105,7 +109,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// <inheritdoc />
|
||||
public Task<byte[]> DownloadVersion(Version version, CancellationToken cancellationToken)
|
||||
{
|
||||
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Minor);
|
||||
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURLTemplate, version.Major, version.Minor);
|
||||
|
||||
return ioManager.DownloadFile(new Uri(url), cancellationToken);
|
||||
}
|
||||
@@ -118,20 +122,21 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
var configPath = ioManager.ConcatPath(path, ByondConfigDir);
|
||||
await ioManager.CreateDirectory(configPath, cancellationToken).ConfigureAwait(false);
|
||||
await ioManager.WriteAllBytes(ioManager.ConcatPath(configPath, ByondDDConfig), Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode), cancellationToken).ConfigureAwait(false);
|
||||
};
|
||||
}
|
||||
|
||||
var setNoPromptTrustedModeTask = SetNoPromptTrusted();
|
||||
|
||||
//after this version lummox made DD depend of directx lol
|
||||
//but then he became amazing and not only fixed it but also gave us 30s compiles \[T]/
|
||||
// after this version lummox made DD depend of directx lol
|
||||
// but then he became amazing and not only fixed it but also gave us 30s compiles \[T]/
|
||||
if (version.Major == 512 && version.Minor >= 1427 && version.Minor < 1452 && !installedDirectX)
|
||||
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
//check again because race conditions
|
||||
if (!installedDirectX)
|
||||
{
|
||||
//always install it, it's pretty fast and will do better redundancy checking than us
|
||||
// ^check again because race conditions
|
||||
// always install it, it's pretty fast and will do better redundancy checking than us
|
||||
var rbdx = ioManager.ConcatPath(path, ByondDXDir);
|
||||
//noShellExecute because we aren't doing runas shennanigans
|
||||
|
||||
// noShellExecute because we aren't doing runas shennanigans
|
||||
IProcess directXInstaller;
|
||||
try
|
||||
{
|
||||
@@ -141,6 +146,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
{
|
||||
throw new JobException("Unable to start DirectX installer process! Is the server running with admin privileges?", e);
|
||||
}
|
||||
|
||||
using (directXInstaller)
|
||||
{
|
||||
int exitCode;
|
||||
|
||||
@@ -177,6 +177,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
else
|
||||
task = Task.CompletedTask;
|
||||
}
|
||||
|
||||
await task.ConfigureAwait(false);
|
||||
return provider;
|
||||
}
|
||||
@@ -190,7 +191,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken)
|
||||
{
|
||||
//map the channel if it's private and we haven't seen it
|
||||
// map the channel if it's private and we haven't seen it
|
||||
lock (providers)
|
||||
{
|
||||
var providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First();
|
||||
@@ -219,7 +220,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
else
|
||||
{
|
||||
//need to add tag and isAdminChannel
|
||||
// need to add tag and isAdminChannel
|
||||
var mapping = enumerable.First().Value;
|
||||
message.User.Channel.Id = mapping.Channel.Id;
|
||||
message.User.Channel.Tag = mapping.Channel.Tag;
|
||||
@@ -236,8 +237,8 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
|
||||
var addressed = address == CommonMention.ToUpperInvariant() || address == provider.BotMention.ToUpperInvariant();
|
||||
|
||||
// no mention
|
||||
if (!addressed && !message.User.Channel.IsPrivateChannel)
|
||||
//no mention
|
||||
return;
|
||||
|
||||
logger.LogTrace("Chat command: {0}. User (True provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User));
|
||||
@@ -247,7 +248,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
|
||||
if (splits.Count == 0)
|
||||
{
|
||||
//just a mention
|
||||
// just a mention
|
||||
await SendMessage("Hi!", new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
@@ -266,8 +267,9 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
handler = tasks.SelectMany(x => x.Result).Where(x => x.Name.ToUpperInvariant() == commandName).FirstOrDefault();
|
||||
}
|
||||
|
||||
return handler;
|
||||
};
|
||||
}
|
||||
|
||||
const string UnknownCommandMessage = "Unknown command! Type '?' or 'help' for available commands.";
|
||||
|
||||
@@ -290,6 +292,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
else
|
||||
helpText = UnknownCommandMessage;
|
||||
}
|
||||
|
||||
await SendMessage(helpText, new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
@@ -318,7 +321,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//error bc custom commands should reply about why it failed
|
||||
// error bc custom commands should reply about why it failed
|
||||
logger.LogError("Error processing chat command: {0}", e);
|
||||
await SendMessage("Internal error processing command!", new List<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -336,11 +339,11 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
//prune disconnected providers
|
||||
// prune disconnected providers
|
||||
foreach (var I in messageTasks.Where(x => !x.Key.Connected).ToList())
|
||||
messageTasks.Remove(I.Key);
|
||||
|
||||
//add new ones
|
||||
// add new ones
|
||||
Task updatedTask;
|
||||
lock (this)
|
||||
updatedTask = connectionsUpdated.Task;
|
||||
@@ -355,10 +358,10 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
continue;
|
||||
}
|
||||
|
||||
//wait for a message
|
||||
// wait for a message
|
||||
await Task.WhenAny(updatedTask, Task.WhenAny(messageTasks.Select(x => x.Value))).ConfigureAwait(false);
|
||||
|
||||
//process completed ones
|
||||
// process completed ones
|
||||
foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList())
|
||||
{
|
||||
var message = await I.Value.ConfigureAwait(false);
|
||||
@@ -385,7 +388,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
if (provider == null)
|
||||
return;
|
||||
var results = await provider.MapChannels(newChannels, cancellationToken).ConfigureAwait(false);
|
||||
if (results == null) //aborted
|
||||
if (results == null) // aborted
|
||||
return;
|
||||
var mappings = Enumerable.Zip(newChannels, results, (x, y) => new ChannelMapping
|
||||
{
|
||||
@@ -407,7 +410,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
lock (mappedChannels)
|
||||
{
|
||||
lock (providers)
|
||||
if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) //aborted again
|
||||
if (!providers.TryGetValue(connectionId, out IProvider verify) || verify != provider) // aborted again
|
||||
return;
|
||||
foreach (var I in mappings)
|
||||
{
|
||||
@@ -419,6 +422,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
lock (trackingContexts)
|
||||
task = Task.WhenAll(trackingContexts.Select(x => x.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken)));
|
||||
}
|
||||
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -444,7 +448,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
Task disconnectTask;
|
||||
lock (providers)
|
||||
{
|
||||
//raw settings changes forces a rebuild of the provider
|
||||
// raw settings changes forces a rebuild of the provider
|
||||
if (providers.TryGetValue(newSettings.Id, out provider))
|
||||
{
|
||||
providers.Remove(newSettings.Id);
|
||||
@@ -471,7 +475,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
await provider.Connect(cancellationToken).ConfigureAwait(false);
|
||||
lock (this)
|
||||
{
|
||||
//same thread shennanigans
|
||||
// same thread shennanigans
|
||||
var oldOne = connectionsUpdated;
|
||||
connectionsUpdated = new TaskCompletionSource<object>();
|
||||
oldOne.SetResult(null);
|
||||
@@ -506,7 +510,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
List<ulong> wdChannels;
|
||||
message = String.Format(CultureInfo.InvariantCulture, "WD: {0}", message);
|
||||
lock (mappedChannels) //so it doesn't change while we're using it
|
||||
lock (mappedChannels) // so it doesn't change while we're using it
|
||||
wdChannels = mappedChannels.Where(x => x.Value.IsWatchdogChannel).Select(x => x.Key).ToList();
|
||||
return SendMessage(message, wdChannels, cancellationToken);
|
||||
}
|
||||
@@ -516,7 +520,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
List<ulong> wdChannels;
|
||||
message = String.Format(CultureInfo.InvariantCulture, "DM: {0}", message);
|
||||
lock (mappedChannels) //so it doesn't change while we're using it
|
||||
lock (mappedChannels) // so it doesn't change while we're using it
|
||||
wdChannels = mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList();
|
||||
return SendMessage(message, wdChannels, cancellationToken);
|
||||
}
|
||||
@@ -547,11 +551,17 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
if (customCommandHandler == null)
|
||||
throw new InvalidOperationException("RegisterCommandHandler() hasn't been called!");
|
||||
JsonTrackingContext context = null;
|
||||
context = new JsonTrackingContext(ioManager, customCommandHandler, loggerFactory.CreateLogger<JsonTrackingContext>(), () =>
|
||||
{
|
||||
lock (trackingContexts)
|
||||
trackingContexts.Remove(context);
|
||||
}, ioManager.ConcatPath(basePath, commandsJsonName), ioManager.ConcatPath(basePath, channelsJsonName));
|
||||
context = new JsonTrackingContext(
|
||||
ioManager,
|
||||
customCommandHandler,
|
||||
loggerFactory.CreateLogger<JsonTrackingContext>(),
|
||||
() =>
|
||||
{
|
||||
lock (trackingContexts)
|
||||
trackingContexts.Remove(context);
|
||||
},
|
||||
ioManager.ConcatPath(basePath, commandsJsonName),
|
||||
ioManager.ConcatPath(basePath, channelsJsonName));
|
||||
Task task;
|
||||
lock (trackingContexts)
|
||||
{
|
||||
@@ -559,6 +569,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
lock (mappedChannels)
|
||||
task = context.SetChannels(mappedChannels.Select(y => y.Value.Channel), cancellationToken);
|
||||
}
|
||||
|
||||
await task.ConfigureAwait(false);
|
||||
return context;
|
||||
}
|
||||
@@ -598,7 +609,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
var message = updateVersion == null ? "TGS: Restart requested..." : String.Format(CultureInfo.InvariantCulture, "TGS: Updating to version {0}...", updateVersion);
|
||||
List<ulong> wdChannels;
|
||||
lock (mappedChannels) //so it doesn't change while we're using it
|
||||
lock (mappedChannels) // so it doesn't change while we're using it
|
||||
wdChannels = mappedChannels.Select(x => x.Key).ToList();
|
||||
return SendMessage(message, wdChannels, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
return "Repository unavailable!";
|
||||
head = repo.Head;
|
||||
}
|
||||
|
||||
await databaseContextFactory.UseContext(async db => results = await db.RevisionInformations.Where(x => x.Instance.Id == instance.Id && x.CommitSha == head)
|
||||
.SelectMany(x => x.ActiveTestMerges)
|
||||
.Select(x => x.TestMerge)
|
||||
@@ -89,10 +90,7 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
results = watchdog.ActiveCompileJob?.RevisionInformation.ActiveTestMerges.Select(x => x.TestMerge).ToList() ?? new List<Models.TestMerge>();
|
||||
}
|
||||
|
||||
if (!results.Any())
|
||||
return "None!";
|
||||
|
||||
return String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7))));
|
||||
return !results.Any() ? "None!" : String.Join(", ", results.Select(x => String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
logger.LogWarning("Error retrieving custom commands! Exception: {0}", e);
|
||||
}
|
||||
|
||||
return new List<CustomCommand>();
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// </summary>
|
||||
/// <param name="fromDiscord">The mention <see cref="string"/> provided by the Discord library</param>
|
||||
/// <returns>The normalized mention <see cref="string"/></returns>
|
||||
static string NormalizeMention(string fromDiscord) => fromDiscord.Replace("!", "", StringComparison.Ordinal);
|
||||
static string NormalizeMention(string fromDiscord) => fromDiscord.Replace("!", String.Empty, StringComparison.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DiscordProvider"/>
|
||||
@@ -100,7 +100,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
IsPrivateChannel = pm,
|
||||
ConnectionName = pm ? e.Author.Username : (e.Channel as ITextChannel)?.Guild.Name ?? "UNKNOWN",
|
||||
FriendlyName = e.Channel.Name
|
||||
//isAdmin and Tag populated by manager
|
||||
|
||||
// isAdmin and Tag populated by manager
|
||||
},
|
||||
FriendlyName = e.Author.Username,
|
||||
Mention = NormalizeMention(e.Author.Mention)
|
||||
@@ -182,19 +183,19 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
if (!channel.DiscordChannelId.HasValue)
|
||||
throw new InvalidOperationException("ChatChannel missing DiscordChannelId!");
|
||||
|
||||
if (!(client.GetChannel(channel.DiscordChannelId.Value) is ITextChannel discordChannel))
|
||||
return null;
|
||||
if (client.GetChannel(channel.DiscordChannelId.Value) is ITextChannel discordChannel)
|
||||
return new Channel
|
||||
{
|
||||
RealId = discordChannel.Id,
|
||||
IsAdminChannel = channel.IsAdminChannel == true,
|
||||
ConnectionName = discordChannel.Guild.Name,
|
||||
FriendlyName = discordChannel.Name,
|
||||
IsPrivateChannel = false,
|
||||
Tag = channel.Tag
|
||||
};
|
||||
|
||||
return new Channel
|
||||
{
|
||||
RealId = discordChannel.Id,
|
||||
IsAdminChannel = channel.IsAdminChannel == true,
|
||||
ConnectionName = discordChannel.Guild.Name,
|
||||
FriendlyName = discordChannel.Name,
|
||||
IsPrivateChannel = false,
|
||||
Tag = channel.Tag
|
||||
};
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
var enumerator = channels.Select(x => GetChannelForChatChannel(x)).Where(x => x != null).ToList();
|
||||
|
||||
|
||||
@@ -44,18 +44,22 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// Address of the server to connect to
|
||||
/// </summary>
|
||||
readonly string address;
|
||||
|
||||
/// <summary>
|
||||
/// Port of the server to connect to
|
||||
/// </summary>
|
||||
readonly ushort port;
|
||||
|
||||
/// <summary>
|
||||
/// IRC nickname
|
||||
/// </summary>
|
||||
readonly string nickname;
|
||||
|
||||
/// <summary>
|
||||
/// Password which will used for authentication
|
||||
/// </summary>
|
||||
readonly string password;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IrcPasswordType"/> of <see cref="password"/>
|
||||
/// </summary>
|
||||
@@ -134,7 +138,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
UseSsl = useSsl
|
||||
};
|
||||
if (useSsl)
|
||||
client.ValidateServerCertificate = true; //dunno if it defaults to that or what
|
||||
client.ValidateServerCertificate = true; // dunno if it defaults to that or what
|
||||
|
||||
client.OnChannelMessage += Client_OnChannelMessage;
|
||||
client.OnQueryMessage += Client_OnQueryMessage;
|
||||
@@ -151,7 +155,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
if (Connected)
|
||||
{
|
||||
disconnecting = true;
|
||||
client.Disconnect(); //just closes the socket
|
||||
client.Disconnect(); // just closes the socket
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,8 +188,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
if (dicToCheck == queryChannelIdMap)
|
||||
channelIdMap.Add(resultId.Value, null);
|
||||
}
|
||||
|
||||
return resultId.Value;
|
||||
};
|
||||
}
|
||||
|
||||
ulong userId, channelId;
|
||||
lock (this)
|
||||
@@ -205,7 +210,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
FriendlyName = isPrivate ? String.Format(CultureInfo.InvariantCulture, "PM: {0}", channelName) : channelName,
|
||||
RealId = channelId,
|
||||
IsPrivateChannel = isPrivate
|
||||
//isAdmin and Tag populated by manager
|
||||
|
||||
// isAdmin and Tag populated by manager
|
||||
},
|
||||
FriendlyName = username,
|
||||
RealId = userId,
|
||||
@@ -247,7 +253,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
if (passwordType == IrcPasswordType.Sasl)
|
||||
{
|
||||
client.WriteLine("CAP REQ :sasl", Priority.Critical); //needs to be put in the buffer before anything else
|
||||
client.WriteLine("CAP REQ :sasl", Priority.Critical); // needs to be put in the buffer before anything else
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
client.Login(nickname, nickname, 0, nickname);
|
||||
@@ -260,7 +266,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
else if (passwordType == IrcPasswordType.Sasl)
|
||||
{
|
||||
//wait for the sasl ack or timeout
|
||||
// wait for the sasl ack or timeout
|
||||
var recievedAck = false;
|
||||
var recievedPlus = false;
|
||||
client.OnReadLine += (sender, e) =>
|
||||
@@ -285,7 +291,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
for (; !recievedPlus && DateTimeOffset.Now <= endTime; asyncDelayer.Delay(listenTimeSpan, cancellationToken).GetAwaiter().GetResult())
|
||||
client.Listen(false);
|
||||
|
||||
//Stolen! https://github.com/znc/znc/blob/1e697580155d5a38f8b5a377f3b1d94aaa979539/modules/sasl.cpp#L196
|
||||
// Stolen! https://github.com/znc/znc/blob/1e697580155d5a38f8b5a377f3b1d94aaa979539/modules/sasl.cpp#L196
|
||||
var authString = String.Format(CultureInfo.InvariantCulture, "{0}{1}{0}{1}{2}", nickname, '\0', password);
|
||||
var b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(authString));
|
||||
var authLine = String.Format(CultureInfo.InvariantCulture, "AUTHENTICATE {0}", b64);
|
||||
@@ -306,7 +312,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
if (disconnecting || !client.IsConnected)
|
||||
break;
|
||||
client.Listen(false);
|
||||
//ensure we have the correct nick
|
||||
|
||||
// ensure we have the correct nick
|
||||
if (client.GetIrcUser(nickname) == null)
|
||||
client.RfcNick(nickname);
|
||||
}
|
||||
@@ -336,7 +343,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
try
|
||||
{
|
||||
client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); //priocritical otherwise it wont go through
|
||||
client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); // priocritical otherwise it wont go through
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -363,7 +370,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
throw new InvalidOperationException("ChatChannel missing IrcChannel!");
|
||||
lock (this)
|
||||
{
|
||||
var hs = new HashSet<string>(); //for unique inserts
|
||||
var hs = new HashSet<string>(); // for unique inserts
|
||||
foreach (var I in channels)
|
||||
hs.Add(I.IrcChannel);
|
||||
var toPart = new List<string>();
|
||||
|
||||
@@ -72,17 +72,16 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <inheritdoc />
|
||||
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||
{
|
||||
//ALL valid token and login requests that match a route go through this function
|
||||
//404 is returned before
|
||||
|
||||
// ALL valid token and login requests that match a route go through this function
|
||||
// 404 is returned before
|
||||
if (AuthenticationContext != null && AuthenticationContext.User == null)
|
||||
{
|
||||
//valid token, expired password
|
||||
// valid token, expired password
|
||||
await Unauthorized().ExecuteResultAsync(context).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
//validate the headers
|
||||
// validate the headers
|
||||
try
|
||||
{
|
||||
ApiHeaders = new ApiHeaders(Request.GetTypedHeaders());
|
||||
@@ -103,9 +102,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await BadRequest(new ErrorMessage { Message = "Missing Instance header!" }).ExecuteResultAsync(context).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (AuthenticationContext.InstanceUser == null)
|
||||
{
|
||||
//accessing an instance they don't have access to or one that's disabled
|
||||
// accessing an instance they don't have access to or one that's disabled
|
||||
await Forbid().ExecuteResultAsync(context).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
@@ -120,8 +120,9 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (ModelState?.IsValid == false)
|
||||
{
|
||||
var errorMessages = ModelState.SelectMany(x => x.Value.Errors).Select(x => x.ErrorMessage).ToList();
|
||||
//HACK
|
||||
//do some fuckery to remove RequiredAttribute errors
|
||||
|
||||
// HACK
|
||||
// do some fuckery to remove RequiredAttribute errors
|
||||
for (var I = 0; I < errorMessages.Count; ++I)
|
||||
{
|
||||
var message = errorMessages[I];
|
||||
@@ -131,6 +132,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
--I;
|
||||
}
|
||||
}
|
||||
|
||||
if (errorMessages.Count > 0)
|
||||
{
|
||||
await BadRequest(new ErrorMessage { Message = String.Join(Environment.NewLine, errorMessages) }).ExecuteResultAsync(context).ConfigureAwait(false);
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// <summary>
|
||||
/// Helper for using the <see cref="AuthorizeAttribute"/> with the <see cref="Api.Rights"/> system
|
||||
/// </summary>
|
||||
#pragma warning disable CA1019
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
|
||||
sealed class TgsAuthorizeAttribute : AuthorizeAttribute
|
||||
{
|
||||
@@ -19,54 +20,81 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="AdministrationRights"/>
|
||||
/// </summary>
|
||||
/// <param name="requiredRights">The rights required</param>
|
||||
public TgsAuthorizeAttribute(AdministrationRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights);
|
||||
public TgsAuthorizeAttribute(AdministrationRights requiredRights)
|
||||
{
|
||||
Roles = RightsHelper.RoleNames(requiredRights);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="InstanceManagerRights"/>
|
||||
/// </summary>
|
||||
/// <param name="requiredRights">The rights required</param>
|
||||
public TgsAuthorizeAttribute(InstanceManagerRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights);
|
||||
public TgsAuthorizeAttribute(InstanceManagerRights requiredRights)
|
||||
{
|
||||
Roles = RightsHelper.RoleNames(requiredRights);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="RepositoryRights"/>
|
||||
/// </summary>
|
||||
/// <param name="requiredRights">The rights required</param>
|
||||
public TgsAuthorizeAttribute(RepositoryRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights);
|
||||
public TgsAuthorizeAttribute(RepositoryRights requiredRights)
|
||||
{
|
||||
Roles = RightsHelper.RoleNames(requiredRights);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="ByondRights"/>
|
||||
/// </summary>
|
||||
/// <param name="requiredRights">The rights required</param>
|
||||
public TgsAuthorizeAttribute(ByondRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights);
|
||||
public TgsAuthorizeAttribute(ByondRights requiredRights)
|
||||
{
|
||||
Roles = RightsHelper.RoleNames(requiredRights);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="DreamMakerRights"/>
|
||||
/// </summary>
|
||||
/// <param name="requiredRights">The rights required</param>
|
||||
public TgsAuthorizeAttribute(DreamMakerRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights);
|
||||
public TgsAuthorizeAttribute(DreamMakerRights requiredRights)
|
||||
{
|
||||
Roles = RightsHelper.RoleNames(requiredRights);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="DreamDaemonRights"/>
|
||||
/// </summary>
|
||||
/// <param name="requiredRights">The rights required</param>
|
||||
public TgsAuthorizeAttribute(DreamDaemonRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights);
|
||||
public TgsAuthorizeAttribute(DreamDaemonRights requiredRights)
|
||||
{
|
||||
Roles = RightsHelper.RoleNames(requiredRights);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="ChatBotRights"/>
|
||||
/// </summary>
|
||||
/// <param name="requiredRights">The rights required</param>
|
||||
public TgsAuthorizeAttribute(ChatBotRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights);
|
||||
public TgsAuthorizeAttribute(ChatBotRights requiredRights)
|
||||
{
|
||||
Roles = RightsHelper.RoleNames(requiredRights);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="ConfigurationRights"/>
|
||||
/// </summary>
|
||||
/// <param name="requiredRights">The rights required</param>
|
||||
public TgsAuthorizeAttribute(ConfigurationRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights);
|
||||
public TgsAuthorizeAttribute(ConfigurationRights requiredRights)
|
||||
{
|
||||
Roles = RightsHelper.RoleNames(requiredRights);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="TgsAuthorizeAttribute"/> for <see cref="InstanceUserRights"/>
|
||||
/// </summary>
|
||||
/// <param name="requiredRights">The rights required</param>
|
||||
public TgsAuthorizeAttribute(InstanceUserRights requiredRights) => Roles = RightsHelper.RoleNames(requiredRights);
|
||||
public TgsAuthorizeAttribute(InstanceUserRights requiredRights)
|
||||
{
|
||||
Roles = RightsHelper.RoleNames(requiredRights);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ using System;
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Components;
|
||||
using Tgstation.Server.Host.Components.Byond;
|
||||
@@ -90,19 +89,19 @@ namespace Tgstation.Server.Host.Core
|
||||
if (services == null)
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
|
||||
//needful
|
||||
// needful
|
||||
services.AddSingleton<IApplication>(this);
|
||||
|
||||
//configure configuration
|
||||
// configure configuration
|
||||
services.Configure<UpdatesConfiguration>(configuration.GetSection(UpdatesConfiguration.Section));
|
||||
services.Configure<DatabaseConfiguration>(configuration.GetSection(DatabaseConfiguration.Section));
|
||||
services.Configure<GeneralConfiguration>(configuration.GetSection(GeneralConfiguration.Section));
|
||||
services.Configure<FileLoggingConfiguration>(configuration.GetSection(FileLoggingConfiguration.Section));
|
||||
|
||||
//enable options which give us config reloading
|
||||
// enable options which give us config reloading
|
||||
services.AddOptions();
|
||||
|
||||
//other stuff needed for for setup wizard and configuration
|
||||
|
||||
// other stuff needed for for setup wizard and configuration
|
||||
services.AddSingleton<IIOManager, DefaultIOManager>();
|
||||
services.AddSingleton<IConsole, IO.Console>();
|
||||
services.AddSingleton<IDBConnectionFactory, DBConnectionFactory>();
|
||||
@@ -116,24 +115,23 @@ namespace Tgstation.Server.Host.Core
|
||||
IIOManager ioManager;
|
||||
IPlatformIdentifier platformIdentifier;
|
||||
|
||||
//temporarily build the service provider in it's current state
|
||||
//do it here so we can run the setup wizard if necessary
|
||||
//also allows us to get some options and other services we need for continued configuration
|
||||
// temporarily build the service provider in it's current state
|
||||
// do it here so we can run the setup wizard if necessary
|
||||
// also allows us to get some options and other services we need for continued configuration
|
||||
using (var provider = services.BuildServiceProvider())
|
||||
{
|
||||
//run the wizard if necessary
|
||||
// run the wizard if necessary
|
||||
var setupWizard = provider.GetRequiredService<ISetupWizard>();
|
||||
var applicationLifetime = provider.GetRequiredService<Microsoft.AspNetCore.Hosting.IApplicationLifetime>();
|
||||
var setupWizardRan = setupWizard.CheckRunWizard(applicationLifetime.ApplicationStopping).GetAwaiter().GetResult();
|
||||
|
||||
//load the configuration options we need
|
||||
// load the configuration options we need
|
||||
var generalOptions = provider.GetRequiredService<IOptions<GeneralConfiguration>>();
|
||||
generalConfiguration = generalOptions.Value;
|
||||
|
||||
//unless this is set, in which case, we leave
|
||||
// unless this is set, in which case, we leave
|
||||
if (setupWizardRan && generalConfiguration.SetupWizardMode == SetupWizardMode.Only)
|
||||
//we don't inject a logger in the constuctor to log this because it's not yet configured
|
||||
throw new OperationCanceledException("Exiting due to SetupWizardMode configuration!");
|
||||
throw new OperationCanceledException("Exiting due to SetupWizardMode configuration!"); // we don't inject a logger in the constuctor to log this because it's not yet configured
|
||||
|
||||
var dbOptions = provider.GetRequiredService<IOptions<DatabaseConfiguration>>();
|
||||
databaseConfiguration = dbOptions.Value;
|
||||
@@ -145,11 +143,11 @@ namespace Tgstation.Server.Host.Core
|
||||
platformIdentifier = provider.GetRequiredService<IPlatformIdentifier>();
|
||||
}
|
||||
|
||||
//setup file logging via serilog
|
||||
// setup file logging via serilog
|
||||
if (!fileLoggingConfiguration.Disable)
|
||||
services.AddLogging(builder =>
|
||||
{
|
||||
//common app data is C:/ProgramData on windows, else /usr/shar
|
||||
// common app data is C:/ProgramData on windows, else /usr/shar
|
||||
var logPath = !String.IsNullOrEmpty(fileLoggingConfiguration.Directory) ? fileLoggingConfiguration.Directory : ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), VersionPrefix, "Logs");
|
||||
|
||||
logPath = ioManager.ConcatPath(logPath, "tgs-{Date}.log");
|
||||
@@ -175,7 +173,7 @@ namespace Tgstation.Server.Host.Core
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid log level {0}", logLevel));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var logEventLevel = ConvertLogLevel(fileLoggingConfiguration.LogLevel);
|
||||
var microsoftEventLevel = ConvertLogLevel(fileLoggingConfiguration.MicrosoftLogLevel);
|
||||
@@ -195,23 +193,24 @@ namespace Tgstation.Server.Host.Core
|
||||
builder.AddSerilog(configuration.CreateLogger(), true);
|
||||
});
|
||||
|
||||
//configure bearer token validation
|
||||
// configure bearer token validation
|
||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(jwtBearerOptions =>
|
||||
{
|
||||
//this line isn't actually run until the first request is made
|
||||
//at that point tokenFactory will be populated
|
||||
// this line isn't actually run until the first request is made
|
||||
// at that point tokenFactory will be populated
|
||||
jwtBearerOptions.TokenValidationParameters = tokenFactory.ValidationParameters;
|
||||
jwtBearerOptions.Events = new JwtBearerEvents
|
||||
{
|
||||
//Application is our composition root so this monstrosity of a line is okay
|
||||
// Application is our composition root so this monstrosity of a line is okay
|
||||
OnTokenValidated = ctx => ctx.HttpContext.RequestServices.GetRequiredService<IClaimsInjector>().InjectClaimsIntoContext(ctx, ctx.HttpContext.RequestAborted)
|
||||
};
|
||||
});
|
||||
//fucking converts 'sub' to M$ bs
|
||||
//can't be done in the above lambda, that's too late
|
||||
|
||||
// fucking prevents converting 'sub' to M$ bs
|
||||
// can't be done in the above lambda, that's too late
|
||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||
|
||||
//add mvc, configure the json serializer settings
|
||||
// add mvc, configure the json serializer settings
|
||||
services.AddMvc().AddJsonOptions(options =>
|
||||
{
|
||||
options.AllowInputFormatterExceptionMessages = true;
|
||||
@@ -232,7 +231,7 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddScoped<IDatabaseContext>(x => x.GetRequiredService<TContext>());
|
||||
}
|
||||
|
||||
//add the correct database context type
|
||||
// add the correct database context type
|
||||
var dbType = databaseConfiguration.DatabaseType;
|
||||
switch (dbType)
|
||||
{
|
||||
@@ -247,11 +246,11 @@ namespace Tgstation.Server.Host.Core
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType));
|
||||
}
|
||||
|
||||
//configure other database services
|
||||
// configure other database services
|
||||
services.AddSingleton<IDatabaseContextFactory, DatabaseContextFactory>();
|
||||
services.AddSingleton<IDatabaseSeeder, DatabaseSeeder>();
|
||||
|
||||
//configure security services
|
||||
// configure security services
|
||||
services.AddScoped<IAuthenticationContextFactory, AuthenticationContextFactory>();
|
||||
services.AddScoped<IClaimsInjector, ClaimsInjector>();
|
||||
services.AddSingleton<IIdentityCache, IdentityCache>();
|
||||
@@ -259,7 +258,7 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<ITokenFactory, TokenFactory>();
|
||||
services.AddSingleton<IPasswordHasher<Models.User>, PasswordHasher<Models.User>>();
|
||||
|
||||
//configure platform specific services
|
||||
// configure platform specific services
|
||||
if (platformIdentifier.IsWindows)
|
||||
{
|
||||
services.AddSingleton<ISystemIdentityFactory, WindowsSystemIdentityFactory>();
|
||||
@@ -280,7 +279,7 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<INetworkPromptReaper, PosixNetworkPromptReaper>();
|
||||
}
|
||||
|
||||
//configure misc services
|
||||
// configure misc services
|
||||
services.AddSingleton<ISynchronousIOManager, SynchronousIOManager>();
|
||||
services.AddSingleton<IGitHubClientFactory, GitHubClientFactory>();
|
||||
services.AddSingleton<IProcessExecutor, ProcessExecutor>();
|
||||
@@ -290,14 +289,14 @@ namespace Tgstation.Server.Host.Core
|
||||
SendTimeout = generalConfiguration.ByondTopicTimeout
|
||||
});
|
||||
|
||||
//configure component services
|
||||
// configure component services
|
||||
services.AddSingleton<ICredentialsProvider, CredentialsProvider>();
|
||||
services.AddSingleton<IProviderFactory, ProviderFactory>();
|
||||
services.AddSingleton<IChatFactory, ChatFactory>();
|
||||
services.AddSingleton<IWatchdogFactory, WatchdogFactory>();
|
||||
services.AddSingleton<IInstanceFactory, InstanceFactory>();
|
||||
|
||||
//configure root services
|
||||
// configure root services
|
||||
services.AddSingleton<InstanceManager>();
|
||||
services.AddSingleton<IInstanceManager>(x => x.GetRequiredService<InstanceManager>());
|
||||
services.AddSingleton<IHostedService>(x => x.GetRequiredService<InstanceManager>());
|
||||
@@ -326,38 +325,38 @@ namespace Tgstation.Server.Host.Core
|
||||
|
||||
logger.LogInformation(VersionString);
|
||||
|
||||
//attempt to restart the server if the configuration changes
|
||||
// attempt to restart the server if the configuration changes
|
||||
if(serverControl.WatchdogPresent)
|
||||
ChangeToken.OnChange(configuration.GetReloadToken, () => serverControl.Restart());
|
||||
|
||||
//now setup the HTTP request pipeline
|
||||
// setup the HTTP request pipeline
|
||||
|
||||
//should anything after this throw an exception, catch it and display a detailed html page
|
||||
applicationBuilder.UseDeveloperExceptionPage(); //it is not worth it to limit this, you should only ever get it if you're an authorized user
|
||||
|
||||
//suppress OperationCancelledExceptions, they are just aborted HTTP requests
|
||||
// should anything after this throw an exception, catch it and display a detailed html page
|
||||
applicationBuilder.UseDeveloperExceptionPage(); // it is not worth it to limit this, you should only ever get it if you're an authorized user
|
||||
|
||||
// suppress OperationCancelledExceptions, they are just aborted HTTP requests
|
||||
applicationBuilder.UseCancelledRequestSuppression();
|
||||
|
||||
//Do not service requests until Ready is called, this will return 503 until that point
|
||||
// Do not service requests until Ready is called, this will return 503 until that point
|
||||
applicationBuilder.UseAsyncInitialization(async cancellationToken =>
|
||||
{
|
||||
using (cancellationToken.Register(() => startupTcs.SetCanceled()))
|
||||
await startupTcs.Task.ConfigureAwait(false);
|
||||
});
|
||||
|
||||
//authenticate JWT tokens using our security pipeline if present, returns 401 if bad
|
||||
// authenticate JWT tokens using our security pipeline if present, returns 401 if bad
|
||||
applicationBuilder.UseAuthentication();
|
||||
|
||||
//suppress and log database exceptions
|
||||
// suppress and log database exceptions
|
||||
applicationBuilder.UseDbConflictHandling();
|
||||
|
||||
//majority of handling is done in the controllers
|
||||
// majority of handling is done in the controllers
|
||||
applicationBuilder.UseMvc();
|
||||
|
||||
//404 anything that gets this far
|
||||
// 404 anything that gets this far
|
||||
}
|
||||
|
||||
///<inheritdoc />
|
||||
/// <inheritdoc />
|
||||
public void Ready(Exception initializationError)
|
||||
{
|
||||
lock (startupTcs)
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Tgstation.Server.Host.Core
|
||||
/// The <see cref="CancellationTokenSource"/> for <see cref="task"/>
|
||||
/// </summary>
|
||||
readonly CancellationTokenSource cancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Task"/> being run
|
||||
/// </summary>
|
||||
@@ -30,7 +31,7 @@ namespace Tgstation.Server.Host.Core
|
||||
task = job(cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
/// <inehritdoc />
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => cancellationTokenSource.Dispose();
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -69,14 +69,14 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task RunJob(Job job, Func<Job, IDatabaseContext, CancellationToken, Task> operation, CancellationToken cancellationToken)
|
||||
{
|
||||
{
|
||||
try
|
||||
{
|
||||
await databaseContextFactory.UseContext(async databaseContext =>
|
||||
{
|
||||
async Task HandleExceptions(Task task)
|
||||
{
|
||||
void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails);
|
||||
void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails);
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(false);
|
||||
@@ -113,7 +113,7 @@ namespace Tgstation.Server.Host.Core
|
||||
await operation(job, databaseContext, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
logger.LogDebug("Job {0} completed!", job.Id);
|
||||
};
|
||||
}
|
||||
|
||||
await HandleExceptions(RunJobInternal()).ConfigureAwait(false);
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Tgstation.Server.Host.Core
|
||||
|
||||
bool JobErroredOrCancelled() => job.ExceptionDetails != null || job.Cancelled == true;
|
||||
|
||||
//ok so, now it's time for the post commit step if it exists
|
||||
// ok so, now it's time for the post commit step if it exists
|
||||
if (!JobErroredOrCancelled() && job.PostComplete != null)
|
||||
{
|
||||
await HandleExceptions(job.PostComplete(cancellationToken)).ConfigureAwait(false);
|
||||
@@ -186,7 +186,7 @@ namespace Tgstation.Server.Host.Core
|
||||
logger.LogTrace("Starting job manager...");
|
||||
await databaseContextFactory.UseContext(async databaseContext =>
|
||||
{
|
||||
//mark all jobs as cancelled
|
||||
// mark all jobs as cancelled
|
||||
var badJobs = await databaseContext.Jobs.Where(y => !y.StoppedAt.HasValue).Select(y => y.Id).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (badJobs.Count > 0)
|
||||
{
|
||||
@@ -229,10 +229,11 @@ namespace Tgstation.Server.Host.Core
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
//this is fine
|
||||
// this is fine
|
||||
return false;
|
||||
}
|
||||
handler.Cancel(); //this will ensure the db update is only done once
|
||||
|
||||
handler.Cancel(); // this will ensure the db update is only done once
|
||||
await databaseContextFactory.UseContext(async databaseContext =>
|
||||
{
|
||||
job = new Job { Id = job.Id };
|
||||
@@ -240,7 +241,8 @@ namespace Tgstation.Server.Host.Core
|
||||
user = new User { Id = user.Id };
|
||||
databaseContext.Users.Attach(user);
|
||||
job.CancelledBy = user;
|
||||
//let either startup or cancellation set job.cancelled
|
||||
|
||||
// let either startup or cancellation set job.cancelled
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
if (blocking)
|
||||
@@ -274,6 +276,7 @@ namespace Tgstation.Server.Host.Core
|
||||
if (!jobs.TryGetValue(job.Id, out handler))
|
||||
return;
|
||||
}
|
||||
|
||||
Task cancelTask = null;
|
||||
using (jobCancellationToken.Register(() => cancelTask = CancelJob(job, canceller, true, cancellationToken)))
|
||||
await handler.Wait(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tcs.SetResult(exitCode);
|
||||
};
|
||||
return tcs.Task;
|
||||
@@ -68,6 +69,7 @@ namespace Tgstation.Server.Host.Core
|
||||
logger.LogDebug("Unable to get process {0}! Exception: {1}", id, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return new Process(handle, AttachExitHandler(handle), null, null, null, loggerFactory.CreateLogger<Process>(), true);
|
||||
@@ -108,6 +110,7 @@ namespace Tgstation.Server.Host.Core
|
||||
outputStringBuilder.Append(e.Data);
|
||||
};
|
||||
}
|
||||
|
||||
if (readError)
|
||||
{
|
||||
errorStringBuilder = new StringBuilder();
|
||||
|
||||
@@ -38,10 +38,13 @@ namespace Tgstation.Server.Host.Core
|
||||
/// Construct a <see cref="SemaphoreSlimContext"/>
|
||||
/// </summary>
|
||||
/// <param name="lockedSemaphore">The value of <see cref="lockedSemaphore"/></param>
|
||||
SemaphoreSlimContext(SemaphoreSlim lockedSemaphore) => this.lockedSemaphore = lockedSemaphore;
|
||||
SemaphoreSlimContext(SemaphoreSlim lockedSemaphore)
|
||||
{
|
||||
this.lockedSemaphore = lockedSemaphore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalize the <see cref="SemaphoreSlimContext"/>
|
||||
/// Finalizes an instance of the <see cref="SemaphoreSlimContext"/> class.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1821 // Remove empty Finalizers //TODO remove this when https://github.com/dotnet/roslyn-analyzers/issues/1241 is fixed
|
||||
~SemaphoreSlimContext() => Dispose();
|
||||
@@ -58,6 +61,7 @@ namespace Tgstation.Server.Host.Core
|
||||
return;
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
lockedSemaphore.Release();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -158,6 +157,7 @@ namespace Tgstation.Server.Host.Core
|
||||
databaseConfiguration.DatabaseType = databaseType;
|
||||
break;
|
||||
}
|
||||
|
||||
await console.WriteAsync("Invalid database type!", true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
while (true);
|
||||
@@ -171,7 +171,6 @@ namespace Tgstation.Server.Host.Core
|
||||
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync("Enter the database name (Can be from previous installation. Otherwise, should not exist): ", false, cancellationToken).ConfigureAwait(false);
|
||||
string databaseName;
|
||||
|
||||
do
|
||||
{
|
||||
databaseName = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
|
||||
@@ -206,6 +205,7 @@ namespace Tgstation.Server.Host.Core
|
||||
await console.WriteAsync("The account it uses in MSSQL is usually \"NT AUTHORITY\\SYSTEM\" and the role it needs is usually \"dbcreator\".", true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync("We'll run a sanity test here, but it won't be indicative of the service's permissions if that is the case", true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
DbConnection testConnection;
|
||||
@@ -261,7 +261,7 @@ namespace Tgstation.Server.Host.Core
|
||||
using (var command = testConnection.CreateCommand())
|
||||
{
|
||||
command.CommandText = "SELECT VERSION()";
|
||||
var fullVersion = (string)(await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false));
|
||||
var fullVersion = (string)await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Found {0}", fullVersion), true, cancellationToken).ConfigureAwait(false);
|
||||
var splits = fullVersion.Split('-');
|
||||
databaseConfiguration.MySqlServerVersion = splits[0];
|
||||
@@ -276,6 +276,7 @@ namespace Tgstation.Server.Host.Core
|
||||
command.CommandText = String.Format(CultureInfo.InvariantCulture, "CREATE DATABASE {0}", databaseName);
|
||||
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await console.WriteAsync("Success!", true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync("Dropping test database...", true, cancellationToken).ConfigureAwait(false);
|
||||
using (var command = testConnection.CreateCommand())
|
||||
@@ -313,7 +314,8 @@ namespace Tgstation.Server.Host.Core
|
||||
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync("Retrying database configuration...", true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -339,6 +341,7 @@ namespace Tgstation.Server.Host.Core
|
||||
newGeneralConfiguration.MinimumPasswordLength = passwordLength;
|
||||
break;
|
||||
}
|
||||
|
||||
await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
while (true);
|
||||
@@ -355,6 +358,7 @@ namespace Tgstation.Server.Host.Core
|
||||
newGeneralConfiguration.ByondTopicTimeout = topicTimeout;
|
||||
break;
|
||||
}
|
||||
|
||||
await console.WriteAsync("Please enter a positive integer!", true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
while (true);
|
||||
@@ -390,7 +394,8 @@ namespace Tgstation.Server.Host.Core
|
||||
fileLoggingConfiguration.Directory = null;
|
||||
break;
|
||||
}
|
||||
//test a write of it
|
||||
|
||||
// test a write of it
|
||||
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync("Testing directory access...", true, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
@@ -412,6 +417,7 @@ namespace Tgstation.Server.Host.Core
|
||||
await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -424,7 +430,8 @@ namespace Tgstation.Server.Host.Core
|
||||
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync("Please verify the path is valid and you have access to it!", true, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
while (true);
|
||||
|
||||
async Task<LogLevel?> PromptLogLevel(string question)
|
||||
{
|
||||
@@ -432,19 +439,21 @@ namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync(question, true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Enter one of {0}/{1}/{2}/{3}/{4}/{5} (leave blank for default): ",nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Information), nameof(LogLevel.Warning), nameof(LogLevel.Error), nameof(LogLevel.Critical)), false, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Enter one of {0}/{1}/{2}/{3}/{4}/{5} (leave blank for default): ", nameof(LogLevel.Trace), nameof(LogLevel.Debug), nameof(LogLevel.Information), nameof(LogLevel.Warning), nameof(LogLevel.Error), nameof(LogLevel.Critical)), false, cancellationToken).ConfigureAwait(false);
|
||||
var responseString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
|
||||
if (String.IsNullOrWhiteSpace(responseString))
|
||||
return null;
|
||||
if (Enum.TryParse<LogLevel>(responseString, out var logLevel) && logLevel != LogLevel.None)
|
||||
return logLevel;
|
||||
await console.WriteAsync("Invalid log level!", true, cancellationToken).ConfigureAwait(false);
|
||||
} while (true);
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
|
||||
fileLoggingConfiguration.LogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for normal logs (default {0}).", fileLoggingConfiguration.LogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.LogLevel;
|
||||
fileLoggingConfiguration.MicrosoftLogLevel = await PromptLogLevel(String.Format(CultureInfo.InvariantCulture, "Enter the level limit for Microsoft logs (VERY verbose, default {0}).", fileLoggingConfiguration.MicrosoftLogLevel)).ConfigureAwait(false) ?? fileLoggingConfiguration.MicrosoftLogLevel;
|
||||
}
|
||||
|
||||
return fileLoggingConfiguration;
|
||||
}
|
||||
|
||||
@@ -507,7 +516,7 @@ namespace Tgstation.Server.Host.Core
|
||||
|
||||
await console.WriteAsync("Waiting for configuration changes to reload...", true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//we need to wait for the configuration's file system watcher to read and reload the changes
|
||||
// we need to wait for the configuration's file system watcher to read and reload the changes
|
||||
await asyncDelayer.Delay(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -519,7 +528,7 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task RunWizard(string userConfigFileName, CancellationToken cancellationToken)
|
||||
{
|
||||
//welcome message
|
||||
// welcome message
|
||||
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync("Welcome to tgstation-server 4!", true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync("This wizard will help you configure your server.", true, cancellationToken).ConfigureAwait(false);
|
||||
@@ -576,7 +585,6 @@ namespace Tgstation.Server.Host.Core
|
||||
logger.LogTrace("No configuration json detected");
|
||||
}
|
||||
|
||||
|
||||
if (!shouldRunBasedOnAutodetect)
|
||||
{
|
||||
if (forceRun)
|
||||
@@ -586,11 +594,12 @@ namespace Tgstation.Server.Host.Core
|
||||
|
||||
forceRun = await PromptYesNo("Continue running setup wizard? (y/n): ", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
if (!forceRun)
|
||||
|
||||
if (!forceRun)
|
||||
return false;
|
||||
}
|
||||
|
||||
//flush the logs to prevent console conflicts
|
||||
// flush the logs to prevent console conflicts
|
||||
await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await RunWizard(userConfigFileName, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <inheritdoc />
|
||||
public Task<string> ReadLineAsync(bool usePasswordChar, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
{
|
||||
//TODO Make this better: https://stackoverflow.com/questions/9479573/how-to-interrupt-console-readline
|
||||
// TODO: Make this better: https://stackoverflow.com/questions/9479573/how-to-interrupt-console-readline
|
||||
CheckAvailable();
|
||||
if (!usePasswordChar)
|
||||
return System.Console.ReadLine();
|
||||
@@ -46,13 +46,15 @@ namespace Tgstation.Server.Host.IO
|
||||
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
|
||||
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('*');
|
||||
}
|
||||
}
|
||||
while (!cancellationToken.IsCancellationRequested);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
System.Console.WriteLine();
|
||||
return passwordBuilder.ToString();
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -34,18 +33,21 @@ namespace Tgstation.Server.Host.IO
|
||||
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))
|
||||
//this is probably a symlink
|
||||
subDir.Delete();
|
||||
else
|
||||
tasks.Add(NormalizeAndDelete(subDir, cancellationToken));
|
||||
}
|
||||
|
||||
foreach (var file in dir.EnumerateFiles())
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
file.Attributes = FileAttributes.Normal;
|
||||
file.Delete();
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
dir.Delete(true);
|
||||
@@ -84,19 +86,19 @@ namespace Tgstation.Server.Host.IO
|
||||
async Task CopyThisDirectory()
|
||||
{
|
||||
if (!atLeastOneSubDir)
|
||||
await CreateDirectory(dest, cancellationToken).ConfigureAwait(false); //save on createdir calls
|
||||
await CreateDirectory(dest, cancellationToken).ConfigureAwait(false); // save on createdir calls
|
||||
|
||||
var tasks = new List<Task>();
|
||||
|
||||
await dir.EnumerateFiles().ToAsyncEnumerable().ForEachAsync(I =>
|
||||
await dir.EnumerateFiles().ToAsyncEnumerable().ForEachAsync(fileInfo =>
|
||||
{
|
||||
if (ignore != null && ignore.Contains(I.Name))
|
||||
if (ignore != null && ignore.Contains(fileInfo.Name))
|
||||
return;
|
||||
tasks.Add(CopyFile(I.FullName, Path.Combine(dest, I.Name), cancellationToken));
|
||||
tasks.Add(CopyFile(fileInfo.FullName, Path.Combine(dest, fileInfo.Name), cancellationToken));
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
};
|
||||
}
|
||||
|
||||
yield return CopyThisDirectory();
|
||||
}
|
||||
@@ -115,18 +117,6 @@ namespace Tgstation.Server.Host.IO
|
||||
await directoryCopy.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task AppendAllText(string path, string additional_contents, CancellationToken cancellationToken)
|
||||
{
|
||||
if (additional_contents == null)
|
||||
throw new ArgumentNullException(nameof(additional_contents));
|
||||
using (var destStream = new FileStream(ResolvePath(path), FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, DefaultBufferSize, true))
|
||||
{
|
||||
var buf = Encoding.UTF8.GetBytes(additional_contents);
|
||||
await destStream.WriteAsync(buf, 0, buf.Length, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ConcatPath(params string[] paths)
|
||||
{
|
||||
@@ -265,18 +255,10 @@ namespace Tgstation.Server.Host.IO
|
||||
return (IReadOnlyList<string>)results;
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateSymlink(string target, string link, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
{
|
||||
target = ResolvePath(target);
|
||||
link = ResolvePath(link);
|
||||
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<byte[]> DownloadFile(Uri url, CancellationToken cancellationToken)
|
||||
{
|
||||
//DownloadDataTaskAsync can't be cancelled and is shittily written, don't use it
|
||||
// DownloadDataTaskAsync can't be cancelled and is shittily written, don't use it
|
||||
using (var wc = new WebClient())
|
||||
{
|
||||
var tcs = new TaskCompletionSource<byte[]>();
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Tgstation.Server.Host.IO
|
||||
bool DeleteDirectory(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Write <paramref name="data"/> to a file at a given <paramref name="path"/>.
|
||||
/// Write <paramref name="data"/> to a file at a given <paramref name="path"/>
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the file to write</param>
|
||||
/// <param name="data">The new contents of the file</param>
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Tgstation.Server.Host.IO
|
||||
if (filePath == null)
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
|
||||
//set executable bit every time, don't want people calling me when their uploaded "sl" binary doesn't work
|
||||
// set executable bit every time, don't want people calling me when their uploaded "sl" binary doesn't work
|
||||
if (Syscall.stat(filePath, out var stat) != 0)
|
||||
throw new UnixIOException(Stdlib.GetLastError());
|
||||
|
||||
|
||||
@@ -17,15 +17,15 @@ namespace Tgstation.Server.Host.IO
|
||||
/// Construct a <see cref="ResolvingIOManager"/>
|
||||
/// </summary>
|
||||
/// <param name="parent">The <see cref="IIOManager"/> that resolves to the directory to work out of</param>
|
||||
/// <param name="_subdirectory">The value of <see cref="subdirectory"/></param>
|
||||
public ResolvingIOManager(IIOManager parent, string _subdirectory)
|
||||
/// <param name="subdirectory">The value of <see cref="subdirectory"/></param>
|
||||
public ResolvingIOManager(IIOManager parent, string subdirectory)
|
||||
{
|
||||
if (parent == null)
|
||||
throw new ArgumentNullException(nameof(parent));
|
||||
if (_subdirectory == null)
|
||||
throw new ArgumentNullException(nameof(_subdirectory));
|
||||
if (subdirectory == null)
|
||||
throw new ArgumentNullException(nameof(subdirectory));
|
||||
|
||||
subdirectory = ConcatPath(parent.ResolvePath("."), _subdirectory);
|
||||
this.subdirectory = ConcatPath(parent.ResolvePath("."), subdirectory);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -86,8 +86,8 @@ namespace Tgstation.Server.Host.IO
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
//as nice as it would be to not have to arrayify the memory stream, we have to
|
||||
//because, oddly enough sha1(memorystream) != sha1(memorystream.ToArray())
|
||||
// as nice as it would be to not have to arrayify the memory stream, we have to
|
||||
// because, oddly enough sha1(memorystream) != sha1(memorystream.ToArray())
|
||||
// vOv
|
||||
byte[] originalBytes;
|
||||
using (var readMs = new MemoryStream())
|
||||
@@ -95,24 +95,27 @@ namespace Tgstation.Server.Host.IO
|
||||
file.CopyTo(readMs);
|
||||
originalBytes = readMs.ToArray();
|
||||
}
|
||||
|
||||
// no sha1? no write
|
||||
if (originalBytes.Length != 0 && sha1InOut == null)
|
||||
//no sha1? no write
|
||||
return false;
|
||||
|
||||
//suppressed due to only using for consistency checks
|
||||
// suppressed due to only using for consistency checks
|
||||
#pragma warning disable CA5350 // Do not use insecure cryptographic algorithm SHA1.
|
||||
using (var sha1 = new SHA1Managed())
|
||||
#pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1.
|
||||
{
|
||||
string GetSha1(byte[] dataToHash) => dataToHash != null && dataToHash.Length != 0 ? String.Join("", sha1.ComputeHash(dataToHash).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null;
|
||||
string GetSha1(byte[] dataToHash) => dataToHash != null && dataToHash.Length != 0 ? String.Join(String.Empty, sha1.ComputeHash(dataToHash).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null;
|
||||
var originalSha1 = GetSha1(originalBytes);
|
||||
if (originalSha1 != sha1InOut)
|
||||
{
|
||||
sha1InOut = originalSha1;
|
||||
return false;
|
||||
}
|
||||
|
||||
sha1InOut = GetSha1(data);
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (data != null)
|
||||
@@ -124,6 +127,7 @@ namespace Tgstation.Server.Host.IO
|
||||
file.Write(data, 0, data.Length);
|
||||
}
|
||||
}
|
||||
|
||||
if (data == null)
|
||||
File.Delete(path);
|
||||
return true;
|
||||
|
||||
@@ -19,17 +19,20 @@ namespace Tgstation.Server.Host.IO
|
||||
if (linkPath == null)
|
||||
throw new ArgumentNullException(nameof(linkPath));
|
||||
|
||||
//check if its not a file
|
||||
// check if its not a file
|
||||
var flags = File.Exists(targetPath) ? NativeMethods.CreateSymbolicLinkFlags.None : NativeMethods.CreateSymbolicLinkFlags.Directory;
|
||||
|
||||
//no don't fucking use this
|
||||
//sure it works in SOME cases
|
||||
//i.e. win10 1803+ and IN DEVELOPER MODE
|
||||
//other times it throws ERROR_INVALID_PARAMETER
|
||||
//but the fucking worst is there is some configuration of windows that accept the argument and allow the function to succeed
|
||||
//BUT IT DOESN'T CREATE THE FUCKING LINK
|
||||
//I AM NOT DEBUGGING THAT SHIT AGAIN AHHH
|
||||
//flags |= NativeMethods.CreateSymbolicLinkFlags.AllowUnprivilegedCreate;
|
||||
/*
|
||||
* no don't fucking use this
|
||||
* sure it works in SOME cases
|
||||
* i.e. win10 1803+ and IN DEVELOPER MODE
|
||||
* other times it throws ERROR_INVALID_PARAMETER
|
||||
* but the fucking worst is there is some configuration of windows that accept the argument and allow the function to succeed
|
||||
* BUT IT DOESN'T CREATE THE FUCKING LINK
|
||||
* I AM NOT DEBUGGING THAT SHIT AGAIN AHHH
|
||||
|
||||
flags |= NativeMethods.CreateSymbolicLinkFlags.AllowUnprivilegedCreate;
|
||||
*/
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (!NativeMethods.CreateSymbolicLink(linkPath, targetPath, flags))
|
||||
|
||||
@@ -190,10 +190,10 @@ namespace Tgstation.Server.Host.Models
|
||||
|
||||
string targetMigration = null;
|
||||
|
||||
//Update this with new migrations as they are made
|
||||
//Always use the MS class
|
||||
// Update this with new migrations as they are made
|
||||
// Always use the MS class
|
||||
|
||||
//TODO: Uncomment once #816 is merged
|
||||
// TODO: Uncomment once #816 is merged
|
||||
/*
|
||||
if (version < new Version(4, 0, 2))
|
||||
targetMigration = nameof(MSReattachCompileJobRequired);
|
||||
@@ -205,7 +205,7 @@ namespace Tgstation.Server.Host.Models
|
||||
if (UseMySQLMigrations())
|
||||
targetMigration = String.Format(CultureInfo.InvariantCulture, "MY" + targetMigration.Substring(2));
|
||||
|
||||
//even though it clearly implements it in the DatabaseFacade definition this won't work without casting (╯ಠ益ಠ)╯︵ ┻━┻
|
||||
// even though it clearly implements it in the DatabaseFacade definition this won't work without casting (╯ಠ益ಠ)╯︵ ┻━┻
|
||||
var dbServiceProvider = ((IInfrastructure<IServiceProvider>)Database).Instance;
|
||||
var migrator = dbServiceProvider.GetRequiredService<IMigrator>();
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace Tgstation.Server.Host.Models
|
||||
/// </summary>
|
||||
public sealed class Instance : Api.Models.Instance
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.DreamMakerSettings"/> for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
@@ -34,7 +33,7 @@ namespace Tgstation.Server.Host.Models
|
||||
public List<InstanceUser> InstanceUsers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.ChatBot"/>s for the <see cref="Instance"/>
|
||||
/// The <see cref="ChatBot"/>s for the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
public List<ChatBot> ChatSettings { get; set; }
|
||||
|
||||
|
||||
@@ -15,12 +15,16 @@ namespace Tgstation.Server.Host.Models.Migrations
|
||||
/// Path to the json file to use for migrations configuration
|
||||
/// </summary>
|
||||
const string RootJson = "appsettings.json";
|
||||
|
||||
/// <summary>
|
||||
/// Path to the development json file to use for migrations configuration
|
||||
/// </summary>
|
||||
const string DevJson = "appsettings.Development.json";
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Get the <see cref="IOptions{TOptions}"/> for the <see cref="DatabaseConfiguration"/>
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="IOptions{TOptions}"/> for the <see cref="DatabaseConfiguration"/></returns>
|
||||
public static IOptions<DatabaseConfiguration> GetDbContextOptions()
|
||||
{
|
||||
var builder = new ConfigurationBuilder();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Models
|
||||
@@ -26,10 +25,10 @@ namespace Tgstation.Server.Host.Models
|
||||
/// <summary>
|
||||
/// Convert the <see cref="Repository"/> to it's API form
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="Api.Models.Repository"/></returns>
|
||||
/// <returns>A new <see cref="Repository"/></returns>
|
||||
public Repository ToApi() => new Repository
|
||||
{
|
||||
//AccessToken = AccessToken, //never show this
|
||||
// AccessToken = AccessToken, //never show this
|
||||
AccessUser = AccessUser,
|
||||
AutoUpdatesKeepTestMerges = AutoUpdatesKeepTestMerges,
|
||||
AutoUpdatesSynchronize = AutoUpdatesSynchronize,
|
||||
@@ -37,7 +36,8 @@ namespace Tgstation.Server.Host.Models
|
||||
CommitterName = CommitterName,
|
||||
PushTestMergeCommits = PushTestMergeCommits,
|
||||
ShowTestMergeCommitters = ShowTestMergeCommitters
|
||||
//revision information and the rest retrieved by controller
|
||||
|
||||
// revision information and the rest retrieved by controller
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Tgstation.Server.Host.Models
|
||||
ActiveTestMerges = ActiveTestMerges.Select(x => x.TestMerge.ToApi()).ToList(),
|
||||
CompileJobs = CompileJobs.Select(x => new Api.Models.CompileJob
|
||||
{
|
||||
Id = x.Id //anti recursion measure
|
||||
Id = x.Id // anti recursion measure
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Tgstation.Server.Host.Models
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Convert the <see cref="User"/> to it's API form
|
||||
/// Convert the <see cref="User"/> to it's API form
|
||||
/// </summary>
|
||||
/// <param name="showDetails">If rights and system identifier should be shown</param>
|
||||
/// <returns>A new <see cref="Api.Models.User"/></returns>
|
||||
|
||||
@@ -15,7 +15,9 @@ namespace Tgstation.Server.Host
|
||||
/// <summary>
|
||||
/// The <see cref="IServerFactory"/> to use
|
||||
/// </summary>
|
||||
internal static IServerFactory serverFactory = new ServerFactory();
|
||||
#pragma warning disable SA1401 // Fields must be private
|
||||
internal static IServerFactory ServerFactory = new ServerFactory();
|
||||
#pragma warning restore SA1401 // Fields must be private
|
||||
|
||||
/// <summary>
|
||||
/// Entrypoint for the <see cref="Program"/>
|
||||
@@ -25,7 +27,8 @@ namespace Tgstation.Server.Host
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
var listArgs = new List<string>(args);
|
||||
//first arg is 100% always the update path, starting it otherwise is solely for debugging purposes
|
||||
|
||||
// first arg is 100% always the update path, starting it otherwise is solely for debugging purposes
|
||||
string updatePath;
|
||||
if (listArgs.Count > 0)
|
||||
{
|
||||
@@ -38,7 +41,7 @@ namespace Tgstation.Server.Host
|
||||
updatePath = null;
|
||||
try
|
||||
{
|
||||
var server = serverFactory.CreateServer(listArgs.ToArray(), updatePath);
|
||||
var server = ServerFactory.CreateServer(listArgs.ToArray(), updatePath);
|
||||
try
|
||||
{
|
||||
using (var cts = new CancellationTokenSource())
|
||||
@@ -70,6 +73,7 @@ namespace Tgstation.Server.Host
|
||||
File.WriteAllText(updatePath, e.ToString());
|
||||
return 2;
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace Tgstation.Server.Host.Security
|
||||
if (isInstance && InstanceUser == null)
|
||||
return 0;
|
||||
var rightsEnum = RightsHelper.RightToType(rightsType);
|
||||
|
||||
// use the api versions because they're the ones that contain the actual properties
|
||||
var typeToCheck = isInstance ? typeof(InstanceUser) : typeof(User);
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace Tgstation.Server.Host.Security
|
||||
CurrentAuthenticationContext = new AuthenticationContext();
|
||||
return;
|
||||
}
|
||||
|
||||
systemIdentity = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Security
|
||||
if (tokenValidatedContext == null)
|
||||
throw new ArgumentNullException(nameof(tokenValidatedContext));
|
||||
|
||||
//Find the user id in the token
|
||||
// Find the user id in the token
|
||||
var userIdClaim = tokenValidatedContext.Principal.FindFirst(JwtRegisteredClaimNames.Sub);
|
||||
if (userIdClaim == default)
|
||||
throw new InvalidOperationException("Missing required claim!");
|
||||
@@ -65,11 +65,11 @@ namespace Tgstation.Server.Host.Security
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
//we are not responsible for handling header validation issues
|
||||
// we are not responsible for handling header validation issues
|
||||
return;
|
||||
}
|
||||
|
||||
//This populates the CurrentAuthenticationContext field for use by us and subsequent controllers
|
||||
// This populates the CurrentAuthenticationContext field for use by us and subsequent controllers
|
||||
await authenticationContextFactory.CreateAuthenticationContext(userId, apiHeaders.InstanceId, tokenValidatedContext.SecurityToken.ValidFrom, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var authenticationContext = authenticationContextFactory.CurrentAuthenticationContext;
|
||||
@@ -78,9 +78,9 @@ namespace Tgstation.Server.Host.Security
|
||||
var claims = new List<Claim>();
|
||||
foreach (RightsType I in enumerator)
|
||||
{
|
||||
//if there's no instance user, do a weird thing and add all the instance roles
|
||||
//we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid
|
||||
//if user is null that means they got the token with an expired password
|
||||
// if there's no instance user, do a weird thing and add all the instance roles
|
||||
// we need it so we can get to OnActionExecutionAsync where we can properly decide between BadRequest and Forbid
|
||||
// if user is null that means they got the token with an expired password
|
||||
var rightInt = authenticationContext.User == null || (RightsHelper.IsInstanceRight(I) && authenticationContext.InstanceUser == null) ? ~0U : authenticationContext.GetRight(I);
|
||||
var rightEnum = RightsHelper.RightToType(I);
|
||||
var right = (Enum)Enum.ToObject(rightEnum, rightInt);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
@@ -53,14 +54,20 @@ namespace Tgstation.Server.Host.Security
|
||||
/// <inheritdoc />
|
||||
public bool CheckUserPassword(User user, string password)
|
||||
{
|
||||
switch (passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password))
|
||||
var result = passwordHasher.VerifyHashedPassword(user, user.PasswordHash, password);
|
||||
switch (result)
|
||||
{
|
||||
case PasswordVerificationResult.Failed:
|
||||
return false;
|
||||
case PasswordVerificationResult.SuccessRehashNeeded:
|
||||
SetUserPassword(user, password, false);
|
||||
break;
|
||||
case PasswordVerificationResult.Success:
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Password hasher return unknown PasswordVerificationResult: {0}", result));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,8 +61,9 @@ namespace Tgstation.Server.Host.Security
|
||||
if (cachedIdentities.TryGetValue(user.Id, out var identCache))
|
||||
{
|
||||
logger.LogTrace("Expiring previously cached identity...");
|
||||
identCache.Dispose(); //also clears it out
|
||||
identCache.Dispose(); // also clears it out
|
||||
}
|
||||
|
||||
identCache = new IdentityCacheObject(systemIdentity.Clone(), asyncDelayer, () =>
|
||||
{
|
||||
logger.LogDebug("Expiring system identity cache for user {1}", uid, user.Id);
|
||||
|
||||
@@ -73,13 +73,13 @@ namespace Tgstation.Server.Host.Security
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
|
||||
var now = DateTimeOffset.Now;
|
||||
|
||||
var nowUnix = now.ToUnixTimeSeconds();
|
||||
//this prevents validation conflicts down the line
|
||||
//tldr we can (theoretically) send a token the same second we receive it
|
||||
//since unix time rounds down, it looks like it came from before the user changed their password
|
||||
//this happens occasionally in unit tests
|
||||
//just delay a second so we can force a round up
|
||||
|
||||
// this prevents validation conflicts down the line
|
||||
// tldr we can (theoretically) send a token the same second we receive it
|
||||
// since unix time rounds down, it looks like it came from before the user changed their password
|
||||
// this happens occasionally in unit tests
|
||||
// just delay a second so we can force a round up
|
||||
var lpuUnix = user.LastPasswordUpdate?.ToUnixTimeSeconds();
|
||||
if (nowUnix == lpuUnix)
|
||||
await asyncDelayer.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -63,13 +63,13 @@ namespace Tgstation.Server.Host.Security
|
||||
{
|
||||
if (identity != null)
|
||||
{
|
||||
//var newIdentity = (WindowsIdentity)identity.Clone(); //doesn't work because of https://github.com/dotnet/corefx/issues/31841
|
||||
|
||||
var newIdentity = new WindowsIdentity(identity.Token); //the handle is cloned internally
|
||||
// var newIdentity = (WindowsIdentity)identity.Clone(); //doesn't work because of https://github.com/dotnet/corefx/issues/31841
|
||||
var newIdentity = new WindowsIdentity(identity.Token); // the handle is cloned internally
|
||||
|
||||
return new WindowsSystemIdentity(newIdentity);
|
||||
}
|
||||
//can't clone a UP, shouldn't be trying to anyway, cloning is for impersonation
|
||||
|
||||
// can't clone a UP, shouldn't be trying to anyway, cloning is for impersonation
|
||||
throw new InvalidOperationException("Cannot clone a UserPrincipal based WindowsSystemIdentity!");
|
||||
}
|
||||
|
||||
|
||||
@@ -78,12 +78,13 @@ namespace Tgstation.Server.Host.Security
|
||||
}
|
||||
}
|
||||
return principal != null;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if (!TryGetPrincipalFromContextType(ContextType.Machine) && !TryGetPrincipalFromContextType(ContextType.Domain))
|
||||
return null;
|
||||
return (ISystemIdentity)new WindowsSystemIdentity(principal);
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
},
|
||||
cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ISystemIdentity> CreateSystemIdentity(string username, string password, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
|
||||
@@ -105,8 +106,8 @@ namespace Tgstation.Server.Host.Security
|
||||
|
||||
logger.LogTrace("Successfully logged in username {0}!", originalUsername);
|
||||
|
||||
using (var handle = new SafeAccessTokenHandle(token)) //checked internally, windows identity always duplicates the handle when constructed with a userToken
|
||||
return (ISystemIdentity)new WindowsSystemIdentity(new WindowsIdentity(handle.DangerousGetHandle())); //https://github.com/dotnet/corefx/blob/6ed61acebe3214fcf79b4274f2bb9b55c0604a4d/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs#L271
|
||||
using (var handle = new SafeAccessTokenHandle(token)) // checked internally, windows identity always duplicates the handle when constructed with a userToken
|
||||
return (ISystemIdentity)new WindowsSystemIdentity(new WindowsIdentity(handle.DangerousGetHandle())); // https://github.com/dotnet/corefx/blob/6ed61acebe3214fcf79b4274f2bb9b55c0604a4d/src/System.Security.Principal.Windows/src/System/Security/Principal/WindowsIdentity.cs#L271
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,11 +94,11 @@ namespace Tgstation.Server.Host
|
||||
};
|
||||
fsWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
|
||||
using (var webHost = webHostBuilder
|
||||
.UseStartup<Application>()
|
||||
.ConfigureServices(serviceCollection => serviceCollection.AddSingleton<IServerControl>(this))
|
||||
.Build()
|
||||
)
|
||||
.Build())
|
||||
{
|
||||
logger = webHost.Services.GetRequiredService<ILogger<Server>>();
|
||||
await webHost.RunAsync(cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
@@ -127,6 +127,7 @@ namespace Tgstation.Server.Host
|
||||
logger.LogTrace("Aborted due to concurrency conflict!");
|
||||
return false;
|
||||
}
|
||||
|
||||
updating = true;
|
||||
}
|
||||
|
||||
@@ -153,13 +154,14 @@ namespace Tgstation.Server.Host
|
||||
updating = false;
|
||||
try
|
||||
{
|
||||
//important to not leave this directory around if possible
|
||||
// important to not leave this directory around if possible
|
||||
await ioManager.DeleteDirectory(updatePath, default).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception e2)
|
||||
{
|
||||
throw new AggregateException(e, e2);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -203,6 +205,7 @@ namespace Tgstation.Server.Host
|
||||
restartHandlers.Remove(handler);
|
||||
});
|
||||
}
|
||||
|
||||
return new RestartRegistration(() => { });
|
||||
}
|
||||
|
||||
@@ -213,7 +216,7 @@ namespace Tgstation.Server.Host
|
||||
/// Implements <see cref="Restart()"/>
|
||||
/// </summary>
|
||||
/// <param name="newVersion">The <see cref="Version"/> of any potential updates being applied</param>
|
||||
/// <returns></returns>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task Restart(Version newVersion)
|
||||
{
|
||||
CheckSanity(true);
|
||||
@@ -227,6 +230,7 @@ namespace Tgstation.Server.Host
|
||||
logger.LogTrace("Aborted due to concurrency conflict!");
|
||||
return;
|
||||
}
|
||||
|
||||
RestartRequested = true;
|
||||
}
|
||||
|
||||
@@ -237,7 +241,8 @@ namespace Tgstation.Server.Host
|
||||
logger.LogTrace("Running restart handlers...");
|
||||
var cancellationToken = cts.Token;
|
||||
var eventsTask = Task.WhenAll(restartHandlers.Select(x => x.HandleRestart(newVersion, cancellationToken)).ToList());
|
||||
//YA GOT 10 SECONDS
|
||||
|
||||
// YA GOT 10 SECONDS
|
||||
var expiryTask = Task.Delay(TimeSpan.FromSeconds(10));
|
||||
await Task.WhenAny(eventsTask, expiryTask).ConfigureAwait(false);
|
||||
logger.LogTrace("Joining restart handlers...");
|
||||
|
||||
Reference in New Issue
Block a user