Merge pull request #550 from Cyberboss/BYOND

Byond Manager
This commit is contained in:
Jordan Brown
2018-07-24 10:43:17 -04:00
committed by GitHub
28 changed files with 551 additions and 200 deletions
+5 -1
View File
@@ -16,12 +16,15 @@ configuration:
shallow_clone: true
artifacts:
- path: artifacts/ServerConsole
name: Server Console
- path: artifacts/ServerService
name: Server Service
cache:
- ~\.nuget\packages -> **\*.csproj
- C:\ProgramData\chocolatey\bin -> appveyor.yml
- C:\ProgramData\chocolatey\lib -> appveyor.yml
install:
- choco install fciv doxygen.portable codecov
- choco install doxygen.portable codecov
- nuget restore tgstation-server.sln
build:
project: tgstation-server.sln
@@ -53,6 +56,7 @@ after_test:
- ps: build/UploadCoverage.ps1
- ps: build/BuildDox.ps1
- dotnet publish src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj -o ../../artifacts/ServerConsole -c %CONFIGURATION%
- ps: Copy-Item -path "src/Tgstation.Server.Host.Service/bin/$env:CONFIGURATION" -destination ../../artifacts/ServerService -recurse
deploy:
- provider: GitHub
release: "tgstation-server-v$(TGSVersion)"
@@ -5,14 +5,8 @@ using Tgstation.Server.Api.Rights;
namespace Tgstation.Server.Api.Models
{
/// <inheritdoc />
public sealed class Administration : Internal.ServerSettings
public sealed class Administration
{
/// <summary>
/// If the <see cref="DreamDaemon"/> instances will not be stopped when the server exits. Resets to <see langword="false"/> when the server restarts
/// </summary>
[Permissions(ReadRight = AdministrationRights.SoftStop, WriteRight = AdministrationRights.SoftStop)]
public bool SoftStop { get; set; }
/// <summary>
/// The latest available version of the Tgstation.Server.Host assembly from the upstream repository. If <see cref="Version.Minor"/> is higher than <see cref="CurrentVersion"/>'s the update cannot be applied due to API changes
/// </summary>
+1 -13
View File
@@ -9,22 +9,10 @@ namespace Tgstation.Server.Api.Models
[Model(RightsType.Byond, RequiresInstance = true)]
public sealed class Byond
{
/// <summary>
/// The <see cref="ByondStatus"/> for the <see cref="Byond"/> installation
/// </summary>
[Permissions(DenyWrite = true, ReadRight = ByondRights.ReadStatus)]
public ByondStatus ByondStatus { get; set; }
/// <summary>
/// The <see cref="System.Version"/> of the <see cref="Byond"/> installation used for new compiles. Will be <see langword="null"/> if the user does not have permission to view it or there is no BYOND version installed. Only considers the <see cref="Version.Major"/> and <see cref="Version.Minor"/> numbers
/// </summary>
[Permissions(ReadRight = ByondRights.ReadInstalled, WriteRight = ByondRights.ChangeVersion)]
[Permissions(ReadRight = ByondRights.ReadActive, WriteRight = ByondRights.ChangeVersion)]
public Version Version { get; set; }
/// <summary>
/// The <see cref="System.Version"/> of the <see cref="Byond"/> that's currently running the game servers
/// </summary>
[Permissions(DenyWrite = true, ReadRight = ByondRights.ReadPrevious)]
public Version PreviousVersion { get; set; }
}
}
@@ -1,39 +0,0 @@
namespace Tgstation.Server.Api.Models
{
/// <summary>
/// The status of a <see cref="Byond"/> update job
/// </summary>
#pragma warning disable CA1717 // Only FlagsAttribute enums should have plural names
public enum ByondStatus
#pragma warning restore CA1717 // Only FlagsAttribute enums should have plural names
{
/// <summary>
/// No update in progress
/// </summary>
Idle,
/// <summary>
/// Preparing to update
/// </summary>
Starting,
/// <summary>
/// Revision is downloading
/// </summary>
Downloading,
/// <summary>
/// Revision is deflating
/// </summary>
Staging,
/// <summary>
/// Revision is ready and waiting for DreamDaemon reboot
/// </summary>
Staged,
/// <summary>
/// Revision is being applied
/// </summary>
Updating,
/// <summary>
/// User does not have permission to view the <see cref="ByondStatus"/>
/// </summary>
Hidden,
}
}
@@ -1,23 +0,0 @@
using Tgstation.Server.Api.Rights;
namespace Tgstation.Server.Api.Models.Internal
{
/// <summary>
/// Metadata about an installation
/// </summary>
[Model(RightsType.Administration)]
public class ServerSettings
{
/// <summary>
/// Automatically send unhandled exception data to a public collection service. This will be limited to system information, path data, and game code compilation information.
/// </summary>
[Permissions(ReadRight = AdministrationRights.ChangeTelemetry, WriteRight = AdministrationRights.ChangeTelemetry)]
public bool EnableTelemetry { get; set; }
/// <summary>
/// The git repository URL to recieve updates to Tgstation.Server.Host from, must include credentials if necessary. If set to <see langword="null"/> upstream pulls will be disabled entirely. Should be https://github.com/tgstation/tgstation-server or a fork of it
/// </summary>
[Permissions(ReadRight = AdministrationRights.SetUpstreamRepository, WriteRight = AdministrationRights.SetUpstreamRepository)]
public string UpstreamRepository { get; set; }
}
}
@@ -13,24 +13,16 @@ namespace Tgstation.Server.Api.Rights
/// </summary>
None = 0,
/// <summary>
/// User can change <see cref="Models.Internal.ServerSettings.UpstreamRepository"/>
/// </summary>
SetUpstreamRepository = 1,
/// <summary>
/// User can change <see cref="Models.Internal.ServerSettings.EnableTelemetry"/>
/// </summary>
ChangeTelemetry = 2,
/// <summary>
/// User can edit themself and other <see cref="Models.User"/>s
/// </summary>
EditUsers = 4,
EditUsers = 1,
/// <summary>
/// User can change <see cref="Models.Administration.SoftStop"/>
/// User can gracefully restart the host
/// </summary>
SoftStop = 8,
SoftStop = 2,
/// <summary>
/// User can change <see cref="Models.Administration.CurrentVersion"/>
/// </summary>
ChangeVersion = 16
ChangeVersion = 4
}
}
+3 -15
View File
@@ -13,24 +13,12 @@ namespace Tgstation.Server.Api.Rights
/// </summary>
None = 0,
/// <summary>
/// User may check the installed BYOND version
/// User may check the active installed BYOND version
/// </summary>
ReadInstalled = 1,
/// <summary>
/// User may check the previous BYOND version
/// </summary>
ReadPrevious = 2,
ReadActive = 1,
/// <summary>
/// User may change to any BYOND version
/// </summary>
ChangeVersion = 4,
/// <summary>
/// User may cancel a pending installation job
/// </summary>
Cancel = 8,
/// <summary>
/// User may read the <see cref="Models.ByondStatus"/> of the installation job
/// </summary>
ReadStatus = 16,
ChangeVersion = 4
}
}
@@ -21,10 +21,9 @@ namespace Tgstation.Server.Client.Components
/// <summary>
/// Updates the installed BYOND <see cref="Version"/>
/// </summary>
/// <param name="byond">The <see cref="Byond"/> to update</param>
/// <param name="progressCallback">Optional <see cref="Action{T1}"/> taking a <see cref="ByondStatus"/> to run when it changes</param>
/// <param name="version">The <see cref="Version"/> to set to active</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task Update(Byond byond, Action<ByondStatus> progressCallback, CancellationToken cancellationToken);
Task SetActiveVersion(Version version, CancellationToken cancellationToken);
}
}
@@ -0,0 +1,26 @@
using System;
namespace Tgstation.Server.Host.Components
{
/// <inheritdoc />
sealed class ByondExecutableLock : IByondExecutableLock
{
/// <inheritdoc />
public Version Version { get; set; }
/// <inheritdoc />
public string DreamDaemonPath { get; set; }
/// <inheritdoc />
public string DreamMakerPath { get; set; }
//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
/// <inheritdoc />
public void Dispose() { }
/// <inheritdoc />
public void DoNotDeleteThisSession() { }
}
}
@@ -0,0 +1,166 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.IO;
using MemoryStream = System.IO.MemoryStream;
namespace Tgstation.Server.Host.Components
{
/// <inheritdoc />
sealed class ByondManager : IByondManager
{
const string VersionFileName = "Version.txt";
const string ActiveVersionFileName = "ActiveVersion.txt";
const string BinPath = "byond/bin";
/// <inheritdoc />
public Version ActiveVersion { get; private set; }
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="ByondManager"/>
/// </summary>
readonly IIOManager ioManager;
/// <summary>
/// The <see cref="IByondInstaller"/> for the <see cref="ByondManager"/>
/// </summary>
readonly IByondInstaller byondInstaller;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="ByondManager"/>
/// </summary>
readonly ILogger<ByondManager> logger;
/// <summary>
/// Map of byond <see cref="Version"/>s to <see cref="Task"/>s that complete when they are installed
/// </summary>
readonly Dictionary<string, Task> installedVersions;
/// <summary>
/// Construct a <see cref="ByondManager"/>
/// </summary>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="byondInstaller">The value of <see cref="byondInstaller"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
public ByondManager(IIOManager ioManager, IByondInstaller byondInstaller, ILogger<ByondManager> logger)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.byondInstaller = byondInstaller ?? throw new ArgumentNullException(nameof(byondInstaller));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
installedVersions = new Dictionary<string, Task>();
}
static string VersionKey(Version version) => new Version(version.Major, version.Minor).ToString();
async Task InstallVersion(Version version, CancellationToken cancellationToken)
{
var ourTcs = new TaskCompletionSource<object>();
Task inProgressTask;
var versionKey = VersionKey(version);
bool installed;
lock (installedVersions)
{
installed = installedVersions.TryGetValue(versionKey, out inProgressTask);
if (!installed)
installedVersions.Add(versionKey, ourTcs.Task);
}
if(installed)
using (cancellationToken.Register(() => ourTcs.SetCanceled()))
{
await Task.WhenAny(ourTcs.Task, inProgressTask).ConfigureAwait(false);
return;
}
var downloadTask = byondInstaller.DownloadVersion(version, cancellationToken);
//okay up to us to install it then
await ioManager.DeleteDirectory(versionKey, cancellationToken).ConfigureAwait(false);
await ioManager.CreateDirectory(versionKey, cancellationToken).ConfigureAwait(false);
var resolvedPath = ioManager.ResolvePath(versionKey);
using (var zipBytes = new MemoryStream(await downloadTask.ConfigureAwait(false)))
using (var archive = new ZipArchive(zipBytes))
await Task.Factory.StartNew(() => archive.ExtractToDirectory(resolvedPath), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
await byondInstaller.InstallByond(resolvedPath, version, cancellationToken).ConfigureAwait(false);
//make sure to do this last because this is what tells us we have a valid version
await ioManager.WriteAllBytes(ioManager.ConcatPath(versionKey, VersionFileName), Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task ChangeVersion(Version version, CancellationToken cancellationToken)
{
await InstallVersion(version, cancellationToken).ConfigureAwait(false);
await ioManager.WriteAllBytes(ActiveVersionFileName, Encoding.UTF8.GetBytes(version.ToString()), cancellationToken).ConfigureAwait(false);
ActiveVersion = version;
}
/// <inheritdoc />
public async Task<IByondExecutableLock> UseExecutables(Version requiredVersion, CancellationToken cancellationToken)
{
var versionToUse = requiredVersion ?? ActiveVersion;
if (versionToUse == null)
throw new InvalidOperationException("No BYOND versions installed!");
await InstallVersion(requiredVersion, cancellationToken).ConfigureAwait(false);
var versionKey = VersionKey(versionToUse);
return new ByondExecutableLock
{
DreamDaemonPath = ioManager.ResolvePath(ioManager.ConcatPath(versionKey, BinPath, byondInstaller.DreamDaemonName)),
DreamMakerPath = ioManager.ResolvePath(ioManager.ConcatPath(versionKey, BinPath, byondInstaller.DreamMakerName)),
Version = versionToUse
};
}
/// <inheritdoc />
public async Task StartAsync(CancellationToken cancellationToken)
{
var cacheCleanTask = byondInstaller.CleanCache(cancellationToken);
var activeVersionBytesTask = ioManager.ReadAllBytes(ActiveVersionFileName, cancellationToken);
var directories = await ioManager.GetDirectories(".", cancellationToken).ConfigureAwait(false);
async Task ReadVersion(string path)
{
var bytes = await ioManager.ReadAllBytes(ioManager.ConcatPath(path, VersionFileName), cancellationToken).ConfigureAwait(false);
var text = Encoding.UTF8.GetString(bytes);
if (Version.TryParse(text, out var version))
{
var key = VersionKey(version);
lock (installedVersions)
if (!installedVersions.ContainsKey(key))
{
installedVersions.Add(key, Task.CompletedTask);
return;
}
}
await ioManager.DeleteDirectory(path, cancellationToken).ConfigureAwait(false);
};
await Task.WhenAll(directories.Select(x => ReadVersion(x))).ConfigureAwait(false);
var activeVersionString = Encoding.UTF8.GetString(await activeVersionBytesTask.ConfigureAwait(false));
if (Version.TryParse(activeVersionString, out var activeVersion))
ActiveVersion = activeVersion;
else
await ioManager.DeleteFile(ActiveVersionFileName, cancellationToken).ConfigureAwait(false);
await cacheCleanTask.ConfigureAwait(false);
}
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
}
@@ -39,9 +39,9 @@ namespace Tgstation.Server.Host.Components
public CompilerStatus Status { get; private set; }
/// <summary>
/// The <see cref="IByond"/> for <see cref="DreamMaker"/>
/// The <see cref="IByondManager"/> for <see cref="DreamMaker"/>
/// </summary>
readonly IByond byond;
readonly IByondManager byond;
/// <summary>
/// The <see cref="IIOManager"/> for <see cref="DreamMaker"/>
/// </summary>
@@ -82,7 +82,7 @@ namespace Tgstation.Server.Host.Components
/// <param name="application">The value of <see cref="application"/></param>
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
public DreamMaker(IByond byond, IIOManager ioManager, IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, ILogger<DreamMaker> logger)
public DreamMaker(IByondManager byond, IIOManager ioManager, IConfiguration configuration, ISessionControllerFactory sessionControllerFactory, ICompileJobConsumer compileJobConsumer, IApplication application, IEventConsumer eventConsumer, ILogger<DreamMaker> logger)
{
this.byond = byond;
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
@@ -274,7 +274,7 @@ namespace Tgstation.Server.Host.Components
//run compiler, verify api
bool ddVerified;
using (var byondLock = byond.UseExecutables(null))
using (var byondLock = await byond.UseExecutables(null, cancellationToken).ConfigureAwait(false))
{
job.ByondVersion = byondLock.Version.ToString();
@@ -0,0 +1,46 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Tgstation.Server.Host.Components
{
/// <summary>
/// For downloading and installing BYOND extractions for a given system
/// </summary>
interface IByondInstaller
{
/// <summary>
/// Get the file name of the DreamDaemon executable
/// </summary>
string DreamDaemonName { get; }
/// <summary>
/// Get the file name of the DreamMaker executable
/// </summary>
string DreamMakerName { get; }
/// <summary>
/// Download a given BYOND <paramref name="version"/>
/// </summary>
/// <param name="version">The <see cref="Version"/> of BYOND to download</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="byte"/>s of the zipfile</returns>
Task<byte[]> DownloadVersion(Version version, CancellationToken cancellationToken);
/// <summary>
/// Does actions necessary to get an extracted BYOND installation working
/// </summary>
/// <param name="path">The path to the BYOND installation</param>
/// <param name="version">The <see cref="Version"/> of BYOND being installed</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns></returns>
Task InstallByond(string path, Version version, CancellationToken cancellationToken);
/// <summary>
/// Attempts to cleans the BYOND cache folder for the system
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task CleanCache(CancellationToken cancellationToken);
}
}
@@ -1,4 +1,5 @@
using System;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -7,33 +8,26 @@ namespace Tgstation.Server.Host.Components
/// <summary>
/// For managing the BYOND installation
/// </summary>
public interface IByond
public interface IByondManager : IHostedService
{
/// <summary>
/// Change the current BYOND version
/// The currently active BYOND version
/// </summary>
Version ActiveVersion { get; }
/// <summary>
/// Change the active BYOND version
/// </summary>
/// <param name="version">The new <see cref="Version"/></param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
Task ChangeVersion(Version version, CancellationToken cancellationToken);
/// <summary>
/// Get the currently installed BYOND version
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>The current BYOND version</returns>
Task<Version> GetVersion(CancellationToken cancellationToken);
/// <summary>
/// Lock the current installation's location and return a <see cref="IByondExecutableLock"/>
/// </summary>
/// <param name="requiredVersion">The BYOND <see cref="Version"/> required</param>
IByondExecutableLock UseExecutables(Version requiredVersion);
/// <summary>
/// Clears the cache folder
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task ClearCache(CancellationToken cancellationToken);
/// <returns>A <see cref="Task{TResult}"/> resulting in the requested <see cref="IByondExecutableLock"/></returns>
Task<IByondExecutableLock> UseExecutables(Version requiredVersion, CancellationToken cancellationToken);
}
}
@@ -16,9 +16,9 @@ namespace Tgstation.Server.Host.Components
IRepositoryManager RepositoryManager { get; }
/// <summary>
/// The <see cref="IByond"/> for the <see cref="IInstance"/>
/// The <see cref="IByondManager"/> for the <see cref="IInstance"/>
/// </summary>
IByond Byond { get; }
IByondManager ByondManager { get; }
/// <summary>
/// The <see cref="IDreamMaker"/> for the <see cref="IInstance"/>
@@ -16,7 +16,7 @@ namespace Tgstation.Server.Host.Components
public IRepositoryManager RepositoryManager { get; }
/// <inheritdoc />
public IByond Byond { get; }
public IByondManager ByondManager { get; }
/// <inheritdoc />
public IDreamMaker DreamMaker { get; }
@@ -59,11 +59,11 @@ namespace Tgstation.Server.Host.Components
/// </summary>
CancellationTokenSource timerCts;
public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByond byond, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory)
public Instance(Api.Models.Instance metadata, IRepositoryManager repositoryManager, IByondManager byondManager, IDreamMaker dreamMaker, IWatchdog watchdog, IChat chat, IConfiguration configuration, ICompileJobConsumer compileJobConsumer, IDatabaseContextFactory databaseContextFactory, IDmbFactory dmbFactory)
{
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
RepositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
Byond = byond ?? throw new ArgumentNullException(nameof(byond));
ByondManager = byondManager ?? throw new ArgumentNullException(nameof(byondManager));
DreamMaker = dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker));
watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
Chat = chat ?? throw new ArgumentNullException(nameof(chat));
@@ -125,10 +125,10 @@ namespace Tgstation.Server.Host.Components
}
/// <inheritdoc />
public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), Watchdog.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken));
public Task StartAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(metadata.AutoUpdateInterval), ByondManager.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken), Chat.StartAsync(cancellationToken), compileJobConsumer.StartAsync(cancellationToken), Watchdog.StartAsync(cancellationToken));
/// <inheritdoc />
public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken));
public Task StopAsync(CancellationToken cancellationToken) => Task.WhenAll(SetAutoUpdateInterval(null), ByondManager.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken), Chat.StopAsync(cancellationToken), compileJobConsumer.StopAsync(cancellationToken), Watchdog.StopAsync(cancellationToken));
/// <inheritdoc />
public async Task SetAutoUpdateInterval(int? newInterval)
@@ -115,7 +115,7 @@ namespace Tgstation.Server.Host.Components
var repoManager = new RepositoryManager(metadata.RepositorySettings, repoIoManager);
IByond byond = null;
IByondManager byond = null;
var configuration = new Configuration(configurationIoManager, synchronousIOManager, symlinkFactory, loggerFactory.CreateLogger<Configuration>());
var chat = chatFactory.CreateChat();
@@ -0,0 +1,93 @@
using Microsoft.Extensions.Logging;
using System;
using System.Globalization;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.IO;
namespace Tgstation.Server.Host.Components
{
/// <summary>
/// <see cref="IByondInstaller"/> for Posix systems
/// </summary>
sealed class PosixByondInstaller : IByondInstaller
{
/// <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";
/// <summary>
/// Path to the BYOND cache
/// </summary>
const string ByondCachePath = "~/.byond"; //TODO: Verify this is correct!!!!!
/// <inheritdoc />
public string DreamDaemonName => "DreamDaemon";
/// <inheritdoc />
public string DreamMakerName => "DreamMaker";
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="PosixByondInstaller"/>
/// </summary>
readonly IIOManager ioManager;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="PosixByondInstaller"/>
/// </summary>
readonly ILogger<PosixByondInstaller> logger;
/// <summary>
/// Construct a <see cref="WindowsByondInstaller"/>
/// </summary>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
public PosixByondInstaller(IIOManager ioManager, ILogger<PosixByondInstaller> logger)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <inheritdoc />
public async Task CleanCache(CancellationToken cancellationToken)
{
try
{
await ioManager.DeleteDirectory(ByondCachePath, cancellationToken).ConfigureAwait(false);
}
catch (Exception e)
{
logger.LogWarning("Error deleting BYOND cache! Exception: {0}", e);
}
}
/// <inheritdoc />
public async Task<byte[]> DownloadVersion(Version version, CancellationToken cancellationToken)
{
var ourVersion = version;
//lummox is annoying and doesn't like to post linux versions if nothing changed in DreamDaemon/DM
//if this for's exit condition ever triggers, i get to say I told you so
Exception lastException = null;
for (var I = 0; I < 5 && ourVersion.Minor >= 1; ++I, ourVersion = new Version(ourVersion.Major, ourVersion.Minor))
{
try
{
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, ourVersion.Major, ourVersion.Minor);
return await ioManager.DownloadFile(new Uri(url), cancellationToken).ConfigureAwait(false);
}
catch (WebException e)
{
if (!(e.Status == WebExceptionStatus.ProtocolError && e.Response is HttpWebResponse response && response.StatusCode == HttpStatusCode.NotFound))
throw;
lastException = e;
}
}
throw lastException;
}
/// <inheritdoc />
public Task InstallByond(string path, Version version, CancellationToken cancellationToken) => Task.CompletedTask;
}
}
@@ -24,9 +24,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
readonly IExecutor executor;
/// <summary>
/// The <see cref="IByond"/> for the <see cref="SessionControllerFactory"/>
/// The <see cref="IByondManager"/> for the <see cref="SessionControllerFactory"/>
/// </summary>
readonly IByond byond;
readonly IByondManager byond;
/// <summary>
/// The <see cref="IByondTopicSender"/> for the <see cref="SessionControllerFactory"/>
@@ -81,7 +81,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="chat">The value of <see cref="chat"/></param>
/// <param name="loggerFactory">The value of <see cref="loggerFactory"/></param>
public SessionControllerFactory(IExecutor executor, IByond byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, ILoggerFactory loggerFactory, Models.Instance instance)
public SessionControllerFactory(IExecutor executor, IByondManager byond, IByondTopicSender byondTopicSender, IInteropRegistrar interopRegistrar, ICryptographySuite cryptographySuite, IApplication application, IIOManager ioManager, IChat chat, ILoggerFactory loggerFactory, Models.Instance instance)
{
this.executor = executor ?? throw new ArgumentNullException(nameof(executor));
this.byond = byond ?? throw new ArgumentNullException(nameof(byond));
@@ -142,7 +142,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
var chatJsonTrackingContext = await chatJsonTrackingTask.ConfigureAwait(false);
try
{
var byondLock = currentByondLock ?? byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion));
var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false);
try
{
//more sanitization here cause it uses the same scheme
@@ -190,7 +190,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
var chatJsonTrackingContext = await chat.TrackJsons(basePath, reattachInformation.ChatChannelsJson, reattachInformation.ChatCommandsJson, cancellationToken).ConfigureAwait(false);
try
{
var byondLock = byond.UseExecutables(Version.Parse(reattachInformation.Dmb.CompileJob.ByondVersion));
var byondLock = await byond.UseExecutables(Version.Parse(reattachInformation.Dmb.CompileJob.ByondVersion), cancellationToken).ConfigureAwait(false);
try
{
var session = executor.AttachToDreamDaemon(reattachInformation.ProcessId, byondLock);
@@ -0,0 +1,143 @@
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.IO;
namespace Tgstation.Server.Host.Components
{
/// <summary>
/// <see cref="IByondInstaller"/> for windows systems
/// </summary>
sealed class WindowsByondInstaller : IByondInstaller
{
/// <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";
/// <summary>
/// BYOND's DreamDaemon config file in the cfg modification directory
/// </summary>
const string ByondDDConfig = "byond/config/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>
const string ByondDXDir = "byond/directx";
/// <inheritdoc />
public string DreamDaemonName => "dreamdaemon.exe";
/// <inheritdoc />
public string DreamMakerName => "dm.exe";
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="WindowsByondInstaller"/>
/// </summary>
readonly IIOManager ioManager;
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="WindowsByondInstaller"/>
/// </summary>
readonly ILogger<WindowsByondInstaller> logger;
/// <summary>
/// If DirectX was installed
/// </summary>
bool installedDirectX;
/// <summary>
/// Construct a <see cref="WindowsByondInstaller"/>
/// </summary>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
public WindowsByondInstaller(IIOManager ioManager, ILogger<WindowsByondInstaller> logger)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
installedDirectX = false;
}
/// <inheritdoc />
public async Task CleanCache(CancellationToken cancellationToken)
{
try
{
await ioManager.DeleteDirectory(ioManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "byond/cache"), cancellationToken).ConfigureAwait(false);
}
catch (Exception e)
{
logger.LogWarning("Error deleting BYOND cache! Exception: {0}", e);
}
}
/// <inheritdoc />
public Task<byte[]> DownloadVersion(Version version, CancellationToken cancellationToken)
{
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsURL, version.Major, version.Minor);
return ioManager.DownloadFile(new Uri(url), cancellationToken);
}
/// <inheritdoc />
public async Task InstallByond(string path, Version version, CancellationToken cancellationToken)
{
var setNoPromptTrustedModeTask = ioManager.WriteAllBytes(ByondDDConfig, Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode), cancellationToken);
//after this version lummox made DD depend of directx lol
if (version.Major >= 512 && version.Minor >= 1427 && Monitor.TryEnter(this))
try
{
if (!installedDirectX)
//always install it, it's pretty fast and will do better redundancy checking than us
using (var p = new Process())
{
p.StartInfo.Arguments = "/silent";
var rbdx = ioManager.ConcatPath(path, ByondDXDir);
p.StartInfo.FileName = rbdx + "/DXSETUP.exe";
p.StartInfo.UseShellExecute = false;
p.StartInfo.WorkingDirectory = rbdx;
p.EnableRaisingEvents = true;
var tcs = new TaskCompletionSource<object>();
p.Exited += (a, b) => tcs.SetResult(null);
try
{
p.Start();
using (cancellationToken.Register(() =>
{
p.Kill();
tcs.SetCanceled();
}))
await tcs.Task.ConfigureAwait(false);
}
finally
{
try
{
p.Kill();
p.WaitForExit();
}
catch (InvalidOperationException) { }
}
if (p.ExitCode != 0)
throw new Exception("Failed to install included DirectX! Exit code: " + p.ExitCode);
installedDirectX = true;
}
}
finally
{
Monitor.Exit(this);
}
await setNoPromptTrustedModeTask.ConfigureAwait(false);
}
}
}
@@ -154,11 +154,13 @@ namespace Tgstation.Server.Host.Core
{
services.AddSingleton<ISystemIdentityFactory, WindowsSystemIdentityFactory>();
services.AddSingleton<ISymlinkFactory, WindowsSymlinkFactory>();
services.AddSingleton<IByondInstaller, WindowsByondInstaller>();
}
else
{
services.AddSingleton<ISystemIdentityFactory, PosixSystemIdentityFactory>();
services.AddSingleton<ISymlinkFactory, PosixSymlinkFactory>();
services.AddSingleton<IByondInstaller, PosixByondInstaller>();
}
services.AddSingleton<IExecutor, Executor>();
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -243,5 +244,13 @@ namespace Tgstation.Server.Host.IO
link = ResolvePath(link);
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
/// <inheritdoc />
public async Task<byte[]> DownloadFile(Uri url, CancellationToken cancellationToken)
{
using (var wc = new WebClient())
using (cancellationToken.Register(() => wc.CancelAsync()))
return await wc.DownloadDataTaskAsync(url).ConfigureAwait(false);
}
}
}
+10 -1
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -153,5 +154,13 @@ namespace Tgstation.Server.Host.IO
/// <param name="cancellationToken">A <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task MoveFile(string source, string destination, CancellationToken cancellationToken);
/// <summary>
/// Downloads a file from <paramref name="url"/>
/// </summary>
/// <param name="url">The URL to download</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="byte"/>s of the downloaded file</returns>
Task<byte[]> DownloadFile(Uri url, CancellationToken cancellationToken);
}
}
@@ -58,18 +58,22 @@ namespace Tgstation.Server.Host.IO
return false;
}
}
cancellationToken.ThrowIfCancellationRequested();
file.Seek(0, SeekOrigin.Begin);
cancellationToken.ThrowIfCancellationRequested();
file.SetLength(data.Length);
if (data != null)
{
file.Seek(0, SeekOrigin.Begin);
cancellationToken.ThrowIfCancellationRequested();
file.Write(data, 0, data.Length);
cancellationToken.ThrowIfCancellationRequested();
file.SetLength(data.Length);
return true;
cancellationToken.ThrowIfCancellationRequested();
file.Write(data, 0, data.Length);
}
}
if (data == null)
File.Delete(path);
return true;
}
}
}
@@ -14,9 +14,6 @@ namespace Tgstation.Server.Host.Models
/// <inheritdoc />
abstract class DatabaseContext<TParentContext> : DbContext, IDatabaseContext where TParentContext : DbContext
{
/// <inheritdoc />
public DbSet<ServerSettings> ServerSettings { get; set; }
/// <inheritdoc />
public DbSet<User> Users { get; set; }
@@ -126,18 +123,6 @@ namespace Tgstation.Server.Host.Models
base.OnConfiguring(optionsBuilder);
}
/// <inheritdoc />
public async Task<ServerSettings> GetServerSettings(CancellationToken cancellationToken)
{
var settings = await ServerSettings.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (settings == default(ServerSettings))
{
settings = new ServerSettings();
ServerSettings.Add(settings);
}
return settings;
}
/// <inheritdoc />
public async Task Initialize(CancellationToken cancellationToken)
{
@@ -21,11 +21,6 @@ namespace Tgstation.Server.Host.Models
/// </summary>
const string DefaultAdminPassword = "ISolemlySwearToDeleteTheDataDirectory";
/// <summary>
/// The default git repository to pull server updates from
/// </summary>
const string DefaultUpstreamRepository = "https://github.com/tgstation/tgstation-server";
/// <summary>
/// The <see cref="ICryptographySuite"/> for the <see cref="DatabaseContext{TParentContext}"/>
/// </summary>
@@ -60,12 +55,6 @@ namespace Tgstation.Server.Host.Models
public async Task SeedDatabase(IDatabaseContext databaseContext, CancellationToken cancellationToken)
{
SeedAdminUser(databaseContext);
var serverSettings = await databaseContext.GetServerSettings(cancellationToken).ConfigureAwait(false);
serverSettings.EnableTelemetry = true;
serverSettings.UpstreamRepository = DefaultUpstreamRepository;
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
}
@@ -59,13 +59,6 @@ namespace Tgstation.Server.Host.Models
/// </summary>
DbSet<RepositorySettings> RepositorySettings { get; set; }
/// <summary>
/// Get the <see cref="ServerSettings"/> in the <see cref="IDatabaseContext"/>
/// </summary>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="ServerSettings"/> in the <see cref="IDatabaseContext"/></returns>
Task<ServerSettings> GetServerSettings(CancellationToken cancellationToken);
/// <summary>
/// Saves changes made to the <see cref="IDatabaseContext"/>
/// </summary>
@@ -1,11 +0,0 @@
namespace Tgstation.Server.Host.Models
{
/// <inheritdoc />
public sealed class ServerSettings : Api.Models.Internal.ServerSettings
{
/// <summary>
/// The row Id
/// </summary>
public long Id { get; set; }
}
}
@@ -43,7 +43,7 @@ namespace Tgstation.Server.Host.Security.Tests
var authContext = new AuthenticationContext(null, user, instanceUser);
user.AdministrationRights = AdministrationRights.EditUsers;
instanceUser.ByondRights = ByondRights.ChangeVersion | ByondRights.ReadInstalled;
instanceUser.ByondRights = ByondRights.ChangeVersion | ByondRights.ReadActive;
Assert.AreEqual((int)user.AdministrationRights, authContext.GetRight(RightsType.Administration));
Assert.AreEqual((int)instanceUser.ByondRights, authContext.GetRight(RightsType.Byond));
}