Merge pull request #936 from tgstation/FixesForSteamport

Various stuff
This commit is contained in:
Jordan Brown
2020-04-24 20:52:54 -04:00
committed by GitHub
22 changed files with 321 additions and 133 deletions
+11 -8
View File
@@ -1,13 +1,18 @@
.dockerignore
*/bin
*/obj
.git
.github
.gitignore
.vs
.vscode
.travis.yml
.codecov.yml
.dockerignore
.gitignore
.travis.yml
appveyor.yml
README
omnisharp.json
README.md
build/**
!build/analyzers.ruleset
!build/tgs.docker.sh
@@ -15,11 +20,7 @@ build/**
!build/tgs.png
!build/stylecop.json
!build/Version.props
*/bin
*/obj
tests
docs
tools
src/DMAPI
src/Tgstation.Server.Host/ClientApp/build
src/Tgstation.Server.Host/ClientApp/node_modules
@@ -29,3 +30,5 @@ src/Tgstation.Server.Host/tgs.bat
src/Tgstation.Server.Host/tgs.sh
src/Tgstation.Server.Host.Client
src/Tgstation.Server.Host.Service
tests
tools
+7 -5
View File
@@ -361,15 +361,17 @@ Contact project maintainers to get your client added to this list
Note that tgstation-server is NOT a backup solution, the onus is on the server runners.
The `Repository` folder should, by the nature of git, not need to be backed up or should be done so on the remote server if necessary.
The `Repository` folder should be backed up on the remote server, do not rely on the instance copy to store changes.
The `BYOND` and `Game` folders should never be backed up due.
The `BYOND` and `Game` folders should never be backed up due to being intertwined with instance data.
The `Configuration` folder should and database be fully backed up
The `Configuration` folder should be fully backed up.
To restore an installation from backups, first restore the instance `Configuration` folders in their new homes. Then restore the database, modifying the `Path` column in the `Instances` table where necessary to point to the new instances. Then start the server pointed at the new database.
The database should be fully backed up.
Should you end up with a lost database for some reason or want to reattach a detached instance you can reattach an existing folder by creating an empty file named `TGS4_ALLOW_INSTANCE_ATTACH` inside it (This is automatically created when detaching instances). Then create a new instance with that path, this will bypass the empty folder check. Note that this will not restore things such as user permissions, server config options, or deployment metadata. Those must be reconfigured manually
To restore an installation from backups, first restore the instance `Configuration` folder in its new home. Then restore the database, modifying the `Path` column in the `Instances` table where necessary to point to the new instances. Then start the server pointed at the new database.
Should you end up with a lost database for some reason or want to reattach a detached instance you can reattach an existing folder by creating an empty file named `TGS4_ALLOW_INSTANCE_ATTACH` inside it (This is automatically created when detaching instances). Then create a new instance with that path, this will bypass the empty folder check. Note that this will not restore things such as user permissions, server config options, or deployment metadata. Those must be reconfigured manually.
## Troubleshooting
+2 -3
View File
@@ -11,11 +11,10 @@ if($publish_dox){
echo "Cloning https://git@$github_url..."
git clone -b gh-pages --single-branch "https://git@$github_url" "$doxdir" 2>$null
rm -r "$doxdir\*"
Add-Content "$bf\docs\Doxyfile" "`nPROJECT_NUMBER = $version`nINPUT = $bf`nOUTPUT_DIRECTORY = $doxdir`nPROJECT_LOGO = $bf/build/tgs.ico"
}else{
Add-Content "$bf\docs\Doxyfile" "`nPROJECT_NUMBER = $version`nINPUT = $bf`nOUTPUT_DIRECTORY = $doxdir`nPROJECT_LOGO = $bf/build/tgs.ico`nHAVE_DOT=YES"
}
Add-Content "$bf\docs\Doxyfile" "`nPROJECT_NUMBER = $version`nINPUT = $bf`nOUTPUT_DIRECTORY = $doxdir`nPROJECT_LOGO = $bf/build/tgs.ico`nHAVE_DOT=YES"
&"C:\Program Files\doxygen\bin\doxygen.exe" "$bf\docs\Doxyfile"
if($publish_dox){
+20 -7
View File
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-bionic AS build
# install node and npm
# replace shell with bash so we can source files
@@ -18,29 +18,42 @@ RUN . $NVM_DIR/nvm.sh \
ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
WORKDIR /src
# Build web control panel
WORKDIR /repo/build
COPY build/Version.props Version.props
WORKDIR /repo/src/Tgstation.Server.Host
COPY src/Tgstation.Server.Host/ClientApp ClientApp
COPY src/Tgstation.Server.Host/Tgstation.Server.Host.csproj ./
RUN dotnet msbuild -target:NpmBuild
WORKDIR /repo
# Restore nuget packages
COPY tgstation-server.sln ./
COPY src/Tgstation.Server.Host.Console/Tgstation.Server.Host.Console.csproj src/Tgstation.Server.Host.Console/
COPY src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj src/Tgstation.Server.Host.Watchdog/
COPY src/Tgstation.Server.Host/Tgstation.Server.Host.csproj src/Tgstation.Server.Host/
COPY src/Tgstation.Server.Api/Tgstation.Server.Api.csproj src/Tgstation.Server.Api/
RUN dotnet restore -nowarn:MSB3202,nu1503 -p:RestoreUseSkipNonexistentTargets=false
# Final copy for building
COPY . .
#run dos2unix on tgs.docker.sh so we can build without issue on windows
RUN dos2unix build/tgs.docker.sh
WORKDIR /src/src/Tgstation.Server.Host.Console
WORKDIR /repo/src/Tgstation.Server.Host.Console
RUN dotnet publish -c Release -o /app
WORKDIR /src/src/Tgstation.Server.Host
WORKDIR /repo/src/Tgstation.Server.Host
RUN dotnet publish -c Release -o /app/lib/Default && mv /app/lib/Default/appsettings* /app
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-bionic
EXPOSE 80
#needed for byond
@@ -52,7 +65,7 @@ RUN apt-get update \
WORKDIR /app
COPY --from=build /app .
COPY --from=build /src/build/tgs.docker.sh tgs.sh
COPY --from=build /repo/build/tgs.docker.sh tgs.sh
VOLUME ["/config_data", "/tgs_logs", "/app/lib"]
+16 -1
View File
@@ -1,9 +1,24 @@
#!/bin/sh
SCRIPT_VERSION="1.1.0"
echo "tgstation-server 4 container startup script v$SCRIPT_VERSION"
echo "PWD: $PWD"
PROD_CONFIG=/config_data/appsettings.Production.json
HOST_CONFIG=/app/appsettings.Production.json
if [ ! -f $PROD_CONFIG ]; then
echo "$PROD_CONFIG not detected! Creating empty..."
echo "{}" > $PROD_CONFIG
fi
ln -s $PROD_CONFIG /app/appsettings.Production.json
ln -v -s $PROD_CONFIG $HOST_CONFIG
if [ ! -f "$HOST_CONFIG" ]; then
echo "ln failed to create symlink!"
exit 1
fi
echo "Executing console runner..."
exec dotnet Tgstation.Server.Host.Console.dll
@@ -108,9 +108,14 @@ namespace Tgstation.Server.Host.Components
readonly IPlatformIdentifier platformIdentifier;
/// <summary>
/// The <see cref="IRepositoryFactory"/> for the <see cref="InstanceFactory"/>.
/// The <see cref="ILibGit2RepositoryFactory"/> for the <see cref="InstanceFactory"/>.
/// </summary>
readonly IRepositoryFactory repositoryFactory;
readonly ILibGit2RepositoryFactory repositoryFactory;
/// <summary>
/// The <see cref="ILibGit2Commands"/> for the <see cref="InstanceFactory"/>.
/// </summary>
readonly ILibGit2Commands repositoryCommands;
/// <summary>
/// The <see cref="IServerPortProvider"/> for the <see cref="InstanceFactory"/>.
@@ -138,6 +143,7 @@ namespace Tgstation.Server.Host.Components
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/></param>
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/></param>
/// <param name="repositoryFactory">The value of <see cref="repositoryFactory"/>.</param>
/// <param name="repositoryCommands">The value of <see cref="repositoryCommands"/>.</param>
/// <param name="serverPortProvider">The value of <see cref="serverPortProvider"/>.</param>
public InstanceFactory(
IIOManager ioManager,
@@ -157,7 +163,8 @@ namespace Tgstation.Server.Host.Components
INetworkPromptReaper networkPromptReaper,
IGitHubClientFactory gitHubClientFactory,
IPlatformIdentifier platformIdentifier,
IRepositoryFactory repositoryFactory,
ILibGit2RepositoryFactory repositoryFactory,
ILibGit2Commands repositoryCommands,
IServerPortProvider serverPortProvider)
{
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
@@ -178,6 +185,7 @@ namespace Tgstation.Server.Host.Components
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
this.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory));
this.repositoryCommands = repositoryCommands ?? throw new ArgumentNullException(nameof(repositoryCommands));
this.serverPortProvider = serverPortProvider ?? throw new ArgumentNullException(nameof(serverPortProvider));
}
@@ -198,6 +206,7 @@ namespace Tgstation.Server.Host.Components
var eventConsumer = new EventConsumer(configuration);
var repoManager = new RepositoryManager(
repositoryFactory,
repositoryCommands,
repoIoManager,
eventConsumer,
loggerFactory.CreateLogger<Repository.Repository>(),
@@ -0,0 +1,29 @@
using LibGit2Sharp;
using System.Collections.Generic;
namespace Tgstation.Server.Host.Components.Repository
{
/// <summary>
/// For low level interactions with a <see cref="LibGit2Sharp.IRepository"/>.
/// </summary>
interface ILibGit2Commands
{
/// <summary>
/// Runs a blocking fetch operation on a given <paramref name="repository"/>.
/// </summary>
/// <param name="repository">The <see cref="IRepository"/> to fetch with.</param>
/// <param name="refSpecs">The refspecs to fetch.</param>
/// <param name="remote">The <see cref="Remote"/> to fetch.</param>
/// <param name="fetchOptions">The <see cref="FetchOptions"/>.</param>
/// <param name="logMessage">The message to write in the git log.</param>
void Fetch(LibGit2Sharp.IRepository repository, IEnumerable<string> refSpecs, Remote remote, FetchOptions fetchOptions, string logMessage);
/// <summary>
/// Runs a blocking checkout operation on a given <paramref name="repository"/>.
/// </summary>
/// <param name="repository">The <see cref="IRepository"/> to checkout with.</param>
/// <param name="checkoutOptions">The <see cref="CheckoutOptions"/>.</param>
/// <param name="commitish">The git object to checkout.</param>
void Checkout(LibGit2Sharp.IRepository repository, CheckoutOptions checkoutOptions, string commitish);
}
}
@@ -8,7 +8,7 @@ namespace Tgstation.Server.Host.Components.Repository
/// <summary>
/// Factory for creating <see cref="LibGit2Sharp.IRepository"/>s.
/// </summary>
interface IRepositoryFactory : ICredentialsProvider
interface ILibGit2RepositoryFactory : ICredentialsProvider
{
/// <summary>
/// Create an in-memeory <see cref="LibGit2Sharp.IRepository"/>.
@@ -0,0 +1,34 @@
using LibGit2Sharp;
using System;
using System.Collections.Generic;
namespace Tgstation.Server.Host.Components.Repository
{
/// <inheritdoc />
sealed class LibGit2Commands : ILibGit2Commands
{
/// <inheritdoc />
public void Checkout(LibGit2Sharp.IRepository libGit2Repo, CheckoutOptions checkoutOptions, string commitish)
=> Commands.Checkout(libGit2Repo, commitish, checkoutOptions);
/// <inheritdoc />
public void Fetch(
LibGit2Sharp.IRepository libGit2Repo,
IEnumerable<string> refSpecs,
Remote remote,
FetchOptions fetchOptions,
string logMessage)
{
if (libGit2Repo == null)
throw new ArgumentNullException(nameof(libGit2Repo));
if (!(libGit2Repo is LibGit2Sharp.Repository concreteRepo))
throw new ArgumentException("libGit2Repo must be an instance of LibGit2Sharp.Repository!", nameof(libGit2Repo));
if (remote == null)
throw new ArgumentNullException(nameof(remote));
Commands.Fetch(concreteRepo, remote.Name, refSpecs, fetchOptions, logMessage);
}
}
}
@@ -9,18 +9,18 @@ using Tgstation.Server.Host.Jobs;
namespace Tgstation.Server.Host.Components.Repository
{
/// <inheritdoc />
sealed class RepositoryFactory : IRepositoryFactory
sealed class LibGit2RepositoryFactory : ILibGit2RepositoryFactory
{
/// <summary>
/// The <see cref="ILogger"/> for the <see cref="RepositoryFactory"/>.
/// The <see cref="ILogger"/> for the <see cref="LibGit2RepositoryFactory"/>.
/// </summary>
readonly ILogger<RepositoryFactory> logger;
readonly ILogger<LibGit2RepositoryFactory> logger;
/// <summary>
/// Initializes a new instance of the <see cref="RepositoryFactory"/> <see langword="class"/>.
/// Initializes a new instance of the <see cref="LibGit2RepositoryFactory"/> <see langword="class"/>.
/// </summary>
/// <param name="logger">The value of <see cref="logger"/>.</param>
public RepositoryFactory(ILogger<RepositoryFactory> logger)
public LibGit2RepositoryFactory(ILogger<LibGit2RepositoryFactory> logger)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -28,8 +28,18 @@ namespace Tgstation.Server.Host.Components.Repository
/// <inheritdoc />
public LibGit2Sharp.IRepository CreateInMemory()
{
logger.LogTrace("Creating in-memory repository...");
return new LibGit2Sharp.Repository();
logger.LogTrace("Creating in-memory libgit2 repository...");
var repo = new LibGit2Sharp.Repository();
try
{
logger.LogTrace("Successfully created in-memory libgit2 repository.");
return repo;
}
catch
{
repo.Dispose();
throw;
}
}
/// <inheritdoc />
@@ -37,9 +47,13 @@ namespace Tgstation.Server.Host.Components.Repository
{
if (path == null)
throw new ArgumentNullException(nameof(path));
logger.LogTrace("Creating repostory at {0}...", path);
return Task.Factory.StartNew(
() => (LibGit2Sharp.IRepository)new LibGit2Sharp.Repository(path),
return Task.Factory.StartNew<LibGit2Sharp.IRepository>(
() =>
{
logger.LogTrace("Creating libgit2 repostory at {0}...", path);
return new LibGit2Sharp.Repository(path);
},
cancellationToken,
TaskCreationOptions.LongRunning,
TaskScheduler.Current);
@@ -78,7 +92,7 @@ namespace Tgstation.Server.Host.Components.Repository
if (supportsAnonymous)
return new DefaultCredentials();
if (!hasCreds)
if (supportsUserPass)
throw new JobException("Remote does not support anonymous authentication!");
throw new JobException("Server does not support anonymous or username/password authentication!");
@@ -43,21 +43,26 @@ namespace Tgstation.Server.Host.Components.Repository
public string GitHubRepoName { get; }
/// <inheritdoc />
public bool Tracking => Reference != null && repository.Head.IsTracking;
public bool Tracking => Reference != null && libGitRepo.Head.IsTracking;
/// <inheritdoc />
public string Head => repository.Head.Tip.Sha;
public string Head => libGitRepo.Head.Tip.Sha;
/// <inheritdoc />
public string Reference => repository.Head.FriendlyName;
public string Reference => libGitRepo.Head.FriendlyName;
/// <inheritdoc />
public string Origin => repository.Network.Remotes.First().Url;
public string Origin => libGitRepo.Network.Remotes.First().Url;
/// <summary>
/// The <see cref="LibGit2Sharp.IRepository"/> for the <see cref="Repository"/>
/// </summary>
readonly LibGit2Sharp.IRepository repository;
readonly LibGit2Sharp.IRepository libGitRepo;
/// <summary>
/// The <see cref="ILibGit2Commands"/> for the <see cref="Repository"/>.
/// </summary>
readonly ILibGit2Commands commands;
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="Repository"/>
@@ -99,21 +104,32 @@ namespace Tgstation.Server.Host.Components.Repository
/// <summary>
/// Construct a <see cref="Repository"/>
/// </summary>
/// <param name="repository">The value of <see cref="repository"/></param>
/// <param name="libGitRepo">The value of <see cref="libGitRepo"/></param>
/// <param name="commands">The value of <see cref="commands"/>.</param>
/// <param name="ioMananger">The value of <see cref="ioMananger"/></param>
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
/// <param name="credentialsProvider">The value of <see cref="credentialsProvider"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="onDispose">The value if <see cref="onDispose"/></param>
public Repository(LibGit2Sharp.IRepository repository, IIOManager ioMananger, IEventConsumer eventConsumer, ICredentialsProvider credentialsProvider, ILogger<Repository> logger, Action onDispose)
public Repository(
LibGit2Sharp.IRepository libGitRepo,
ILibGit2Commands commands,
IIOManager ioMananger,
IEventConsumer eventConsumer,
ICredentialsProvider credentialsProvider,
ILogger<Repository> logger,
Action onDispose)
{
this.repository = repository ?? throw new ArgumentNullException(nameof(repository));
this.libGitRepo = libGitRepo ?? throw new ArgumentNullException(nameof(libGitRepo));
this.commands = commands ?? throw new ArgumentNullException(nameof(commands));
this.ioMananger = ioMananger ?? throw new ArgumentNullException(nameof(ioMananger));
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.credentialsProvider = credentialsProvider ?? throw new ArgumentNullException(nameof(credentialsProvider));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
IsGitHubRepository = Origin.Contains(GitHubUrl, StringComparison.InvariantCultureIgnoreCase);
if (IsGitHubRepository)
{
GetRepositoryOwnerName(Origin, out var owner, out var name);
@@ -134,7 +150,7 @@ namespace Tgstation.Server.Host.Components.Repository
}
logger.LogTrace("Disposing...");
repository.Dispose();
libGitRepo.Dispose();
onDispose();
}
@@ -191,15 +207,18 @@ namespace Tgstation.Server.Host.Components.Repository
progressReporter(0);
cancellationToken.ThrowIfCancellationRequested();
Commands.Checkout(repository, committish, new CheckoutOptions
{
CheckoutModifiers = CheckoutModifiers.Force,
OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
});
commands.Checkout(
libGitRepo,
new CheckoutOptions
{
CheckoutModifiers = CheckoutModifiers.Force,
OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
},
committish);
cancellationToken.ThrowIfCancellationRequested();
repository.RemoveUntrackedFiles();
libGitRepo.RemoveUntrackedFiles();
}
/// <inheritdoc />
@@ -248,7 +267,7 @@ namespace Tgstation.Server.Host.Components.Repository
var refSpecList = new List<string> { refSpec };
var logMessage = String.Format(CultureInfo.InvariantCulture, "Merge remote pull request #{0}", testMergeParameters.Number);
var originalCommit = repository.Head;
var originalCommit = libGitRepo.Head;
MergeResult result = null;
@@ -261,37 +280,42 @@ namespace Tgstation.Server.Host.Components.Repository
{
logger.LogTrace("Fetching refspec {0}...", refSpec);
var remote = repository.Network.Remotes.First();
var remote = libGitRepo.Network.Remotes.First();
progressReporter(0);
Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, refSpecList, new FetchOptions
{
Prune = true,
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
OnTransferProgress = (a) =>
commands.Fetch(
libGitRepo,
refSpecList,
remote,
new FetchOptions
{
var percentage = 50 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
progressReporter((int)percentage);
return !cancellationToken.IsCancellationRequested;
Prune = true,
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
OnTransferProgress = (a) =>
{
var percentage = 50 * (((float)a.IndexedObjects + a.ReceivedObjects) / (a.TotalObjects * 2));
progressReporter((int)percentage);
return !cancellationToken.IsCancellationRequested;
},
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
},
OnUpdateTips = (a, b, c) => !cancellationToken.IsCancellationRequested,
CredentialsProvider = credentialsProvider.GenerateCredentialsHandler(username, password)
}, logMessage);
logMessage);
}
catch (UserCancelledException) { }
cancellationToken.ThrowIfCancellationRequested();
repository.RemoveUntrackedFiles();
libGitRepo.RemoveUntrackedFiles();
cancellationToken.ThrowIfCancellationRequested();
testMergeParameters.PullRequestRevision = repository.Lookup(testMergeParameters.PullRequestRevision ?? localBranchName).Sha;
testMergeParameters.PullRequestRevision = libGitRepo.Lookup(testMergeParameters.PullRequestRevision ?? localBranchName).Sha;
cancellationToken.ThrowIfCancellationRequested();
logger.LogTrace("Merging {0} into {1}...", testMergeParameters.PullRequestRevision.Substring(0, 7), Reference);
result = repository.Merge(testMergeParameters.PullRequestRevision, sig, new MergeOptions
result = libGitRepo.Merge(testMergeParameters.PullRequestRevision, sig, new MergeOptions
{
CommitOnSuccess = commitMessage == null,
FailOnConflict = true,
@@ -302,7 +326,7 @@ namespace Tgstation.Server.Host.Components.Repository
}
finally
{
repository.Branches.Remove(localBranchName);
libGitRepo.Branches.Remove(localBranchName);
}
cancellationToken.ThrowIfCancellationRequested();
@@ -315,7 +339,7 @@ namespace Tgstation.Server.Host.Components.Repository
cancellationToken.ThrowIfCancellationRequested();
}
repository.RemoveUntrackedFiles();
libGitRepo.RemoveUntrackedFiles();
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
if (result.Status == MergeStatus.Conflicts)
@@ -327,7 +351,7 @@ namespace Tgstation.Server.Host.Components.Repository
if (commitMessage != null && result.Status != MergeStatus.UpToDate)
{
logger.LogTrace("Committing merge: \"{0}\"...", commitMessage);
await Task.Factory.StartNew(() => repository.Commit(commitMessage, sig, sig, new CommitOptions
await Task.Factory.StartNew(() => libGitRepo.Commit(commitMessage, sig, sig, new CommitOptions
{
PrettifyMessage = true
}), cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
@@ -359,7 +383,7 @@ namespace Tgstation.Server.Host.Components.Repository
await eventConsumer.HandleEvent(EventType.RepoCheckout, new List<string> { committish }, cancellationToken).ConfigureAwait(false);
await Task.Factory.StartNew(() =>
{
repository.RemoveUntrackedFiles();
libGitRepo.RemoveUntrackedFiles();
RawCheckout(committish, progressReporter, cancellationToken);
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
}
@@ -373,10 +397,10 @@ namespace Tgstation.Server.Host.Components.Repository
await eventConsumer.HandleEvent(EventType.RepoFetch, Array.Empty<string>(), cancellationToken).ConfigureAwait(false);
await Task.Factory.StartNew(() =>
{
var remote = repository.Network.Remotes.First();
var remote = libGitRepo.Network.Remotes.First();
try
{
Commands.Fetch((LibGit2Sharp.Repository)repository, remote.Name, remote.FetchRefSpecs.Select(x => x.Specification), new FetchOptions
Commands.Fetch((LibGit2Sharp.Repository)libGitRepo, remote.Name, remote.FetchRefSpecs.Select(x => x.Specification), new FetchOptions
{
Prune = true,
OnProgress = (a) => !cancellationToken.IsCancellationRequested,
@@ -408,17 +432,17 @@ namespace Tgstation.Server.Host.Components.Repository
Task PushHeadToTemporaryBranch(string username, string password, Action<int> progressReporter, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
{
logger.LogInformation("Pushing changes to temporary remote branch...");
var branch = repository.CreateBranch(RemoteTemporaryBranchName);
var branch = libGitRepo.CreateBranch(RemoteTemporaryBranchName);
try
{
cancellationToken.ThrowIfCancellationRequested();
var remote = repository.Network.Remotes.First();
var remote = libGitRepo.Network.Remotes.First();
try
{
var forcePushString = String.Format(CultureInfo.InvariantCulture, "+{0}:{0}", branch.CanonicalName);
repository.Network.Push(remote, forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken));
libGitRepo.Network.Push(remote, forcePushString, GeneratePushOptions(progress => progressReporter((int)(0.9f * progress)), username, password, cancellationToken));
var removalString = String.Format(CultureInfo.InvariantCulture, ":{0}", branch.CanonicalName);
repository.Network.Push(remote, removalString, GeneratePushOptions(progress => progressReporter(90 + (int)(0.1f * progress)), username, password, cancellationToken));
libGitRepo.Network.Push(remote, removalString, GeneratePushOptions(progress => progressReporter(90 + (int)(0.1f * progress)), username, password, cancellationToken));
}
catch (UserCancelledException)
{
@@ -431,7 +455,7 @@ namespace Tgstation.Server.Host.Components.Repository
}
finally
{
repository.Branches.Remove(branch);
libGitRepo.Branches.Remove(branch);
}
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
@@ -443,7 +467,7 @@ namespace Tgstation.Server.Host.Components.Repository
if (!Tracking)
throw new JobException("Cannot reset to origin while not on a tracked reference!");
logger.LogTrace("Reset to origin...");
var trackedBranch = repository.Head.TrackedBranch;
var trackedBranch = libGitRepo.Head.TrackedBranch;
await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List<string> { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken).ConfigureAwait(false);
await ResetToSha(trackedBranch.Tip.Sha, progressReporter, cancellationToken).ConfigureAwait(false);
}
@@ -458,16 +482,16 @@ namespace Tgstation.Server.Host.Components.Repository
logger.LogDebug("Reset to sha: {0}", sha.Substring(0, 7));
repository.RemoveUntrackedFiles();
libGitRepo.RemoveUntrackedFiles();
cancellationToken.ThrowIfCancellationRequested();
var gitObject = repository.Lookup(sha, ObjectType.Commit);
var gitObject = libGitRepo.Lookup(sha, ObjectType.Commit);
cancellationToken.ThrowIfCancellationRequested();
if (gitObject == null)
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Cannot reset to non-existent SHA: {0}", sha));
repository.Reset(ResetMode.Hard, gitObject.Peel<Commit>(), new CheckoutOptions
libGitRepo.Reset(ResetMode.Hard, gitObject.Peel<Commit>(), new CheckoutOptions
{
OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
});
@@ -491,7 +515,7 @@ namespace Tgstation.Server.Host.Components.Repository
MergeResult result = null;
Branch trackedBranch = null;
var oldHead = repository.Head;
var oldHead = libGitRepo.Head;
var oldTip = oldHead.Tip;
await Task.Factory.StartNew(() =>
@@ -499,13 +523,13 @@ namespace Tgstation.Server.Host.Components.Repository
if (!Tracking)
throw new JobException("Cannot reset to origin while not on a tracked reference!");
repository.RemoveUntrackedFiles();
libGitRepo.RemoveUntrackedFiles();
cancellationToken.ThrowIfCancellationRequested();
trackedBranch = repository.Head.TrackedBranch;
trackedBranch = libGitRepo.Head.TrackedBranch;
logger.LogDebug("Merge origin/{2}: <{0} ({1})>", committerName, committerEmail, trackedBranch.FriendlyName);
result = repository.Merge(trackedBranch, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions
result = libGitRepo.Merge(trackedBranch, new Signature(new Identity(committerName, committerEmail), DateTimeOffset.Now), new MergeOptions
{
CommitOnSuccess = true,
FailOnConflict = true,
@@ -519,14 +543,14 @@ namespace Tgstation.Server.Host.Components.Repository
if (result.Status == MergeStatus.Conflicts)
{
logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName);
repository.Reset(ResetMode.Hard, oldTip, new CheckoutOptions
libGitRepo.Reset(ResetMode.Hard, oldTip, new CheckoutOptions
{
OnCheckoutProgress = CheckoutProgressHandler(progressReporter)
});
cancellationToken.ThrowIfCancellationRequested();
}
repository.RemoveUntrackedFiles();
libGitRepo.RemoveUntrackedFiles();
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
if (result.Status == MergeStatus.Conflicts)
@@ -566,9 +590,9 @@ namespace Tgstation.Server.Host.Components.Repository
logger.LogTrace("Configuring <{0} ({1})> as author/committer", committerName, committerEmail);
await Task.Factory.StartNew(() =>
{
repository.Config.Set("user.name", committerName);
libGitRepo.Config.Set("user.name", committerName);
cancellationToken.ThrowIfCancellationRequested();
repository.Config.Set("user.email", committerEmail);
libGitRepo.Config.Set("user.email", committerEmail);
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
@@ -592,9 +616,9 @@ namespace Tgstation.Server.Host.Components.Repository
logger.LogTrace("Resetting and cleaning untracked files...");
await Task.Factory.StartNew(() =>
{
repository.RemoveUntrackedFiles();
libGitRepo.RemoveUntrackedFiles();
cancellationToken.ThrowIfCancellationRequested();
repository.Reset(ResetMode.Hard, repository.Head.Tip, new CheckoutOptions
libGitRepo.Reset(ResetMode.Hard, libGitRepo.Head.Tip, new CheckoutOptions
{
OnCheckoutProgress = CheckoutProgressHandler(progress => progressReporter(progress / 10))
});
@@ -620,10 +644,10 @@ namespace Tgstation.Server.Host.Components.Repository
return await Task.Factory.StartNew(() =>
{
var remote = repository.Network.Remotes.First();
var remote = libGitRepo.Network.Remotes.First();
try
{
repository.Network.Push(repository.Head, GeneratePushOptions(FinalReporter, username, password, cancellationToken));
libGitRepo.Network.Push(libGitRepo.Head, GeneratePushOptions(FinalReporter, username, password, cancellationToken));
return true;
}
catch (NonFastForwardException)
@@ -648,18 +672,18 @@ namespace Tgstation.Server.Host.Components.Repository
public Task<bool> IsSha(string committish, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
{
// check if it's a tag
var gitObject = repository.Lookup(committish, ObjectType.Tag);
var gitObject = libGitRepo.Lookup(committish, ObjectType.Tag);
if (gitObject != null)
return false;
cancellationToken.ThrowIfCancellationRequested();
// check if it's a branch
if (repository.Branches[committish] != null)
if (libGitRepo.Branches[committish] != null)
return false;
cancellationToken.ThrowIfCancellationRequested();
// err on the side of references, if we can't look it up, assume its a reference
if (repository.Lookup<Commit>(committish) != null)
if (libGitRepo.Lookup<Commit>(committish) != null)
return true;
return false;
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
@@ -19,9 +19,14 @@ namespace Tgstation.Server.Host.Components.Repository
public bool CloneInProgress { get; private set; }
/// <summary>
/// The <see cref="IRepositoryFactory"/> for the <see cref="RepositoryManager"/>
/// The <see cref="ILibGit2RepositoryFactory"/> for the <see cref="RepositoryManager"/>
/// </summary>
readonly IRepositoryFactory repositoryFactory;
readonly ILibGit2RepositoryFactory repositoryFactory;
/// <summary>
/// The <see cref="ILibGit2Commands"/> for the <see cref="RepositoryManager"/>.
/// </summary>
readonly ILibGit2Commands commands;
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="RepositoryManager"/>
@@ -57,13 +62,15 @@ namespace Tgstation.Server.Host.Components.Repository
/// Construct a <see cref="RepositoryManager"/>
/// </summary>
/// <param name="repositoryFactory">The value of <see cref="repositoryFactory"/>.</param>
/// <param name="commands">The value of <see cref="commands"/>.</param>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
/// <param name="repositoryLogger">The value of <see cref="repositoryLogger"/></param>
/// <param name="logger">The value of <see cref="logger"/></param>
/// <param name="repositorySettings">The value of <see cref="repositorySettings"/></param>
public RepositoryManager(
IRepositoryFactory repositoryFactory,
ILibGit2RepositoryFactory repositoryFactory,
ILibGit2Commands commands,
IIOManager ioManager,
IEventConsumer eventConsumer,
ILogger<Repository> repositoryLogger,
@@ -71,6 +78,7 @@ namespace Tgstation.Server.Host.Components.Repository
RepositorySettings repositorySettings)
{
this.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory));
this.commands = commands ?? throw new ArgumentNullException(nameof(commands));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
this.repositoryLogger = repositoryLogger ?? throw new ArgumentNullException(nameof(repositoryLogger));
@@ -175,12 +183,18 @@ namespace Tgstation.Server.Host.Components.Repository
using (var context = await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
try
{
var repo = await repositoryFactory.CreateFromPath(ioManager.ResolvePath(), cancellationToken).ConfigureAwait(false);
var libGitRepo = await repositoryFactory.CreateFromPath(ioManager.ResolvePath(), cancellationToken).ConfigureAwait(false);
if (repo == null)
if (libGitRepo == null)
return null;
return new Repository(repo, ioManager, eventConsumer, repositoryFactory, repositoryLogger, () =>
return new Repository(
libGitRepo,
commands,
ioManager,
eventConsumer,
repositoryFactory,
repositoryLogger, () =>
{
logger.LogTrace("Releasing semaphore due to Repository disposal...");
semaphore.Release();
@@ -289,7 +289,8 @@ namespace Tgstation.Server.Host.Core
});
// configure component services
services.AddSingleton<IRepositoryFactory, RepositoryFactory>();
services.AddSingleton<ILibGit2RepositoryFactory, LibGit2RepositoryFactory>();
services.AddSingleton<ILibGit2Commands, LibGit2Commands>();
services.AddSingleton<IProviderFactory, ProviderFactory>();
services.AddSingleton<IChatManagerFactory, ChatManagerFactory>();
services.AddSingleton<IInstanceFactory, InstanceFactory>();
@@ -21,27 +21,22 @@ namespace Tgstation.Server.Host.Core
var httpEndpoint = configuration
.GetSection("Kestrel")
.GetSection("Endpoints")
.GetSection("EndPoints")
.GetSection("Http")
.GetSection("Url")
.Value;
HttpApiPort = GetPortFromAddress(httpEndpoint);
}
if (httpEndpoint == null)
throw new InvalidOperationException("Missing required configuration option Kestrel:EndPoints:Http:Url!");
/// <summary>
/// Convert a given <paramref name="address"/> to its port.
/// </summary>
/// <param name="address">The address <see cref="string"/>.</param>
/// <returns>The parsed port.</returns>
static ushort GetPortFromAddress(string address)
{
var splits = address.Split(":", StringSplitOptions.RemoveEmptyEntries);
var splits = httpEndpoint.Split(":", StringSplitOptions.RemoveEmptyEntries);
var portString = splits.Last();
portString = portString.TrimEnd('/');
if (!UInt16.TryParse(portString, out var result))
throw new InvalidOperationException("Failed to parse HTTP API port!");
return result;
throw new InvalidOperationException($"Failed to parse HTTP EndPoint port: {httpEndpoint}");
HttpApiPort = result;
}
}
}
+24
View File
@@ -0,0 +1,24 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
# THIS SHOULD NOT BE USED TO CREATE THE PRODUCTION BUILD IT'S FOR DEBUGGING ONLY
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["src/Tgstation.Server.Host/Tgstation.Server.Host.csproj", "src/Tgstation.Server.Host/"]
COPY ["src/Tgstation.Server.Api/Tgstation.Server.Api.csproj", "src/Tgstation.Server.Api/"]
RUN dotnet restore "src/Tgstation.Server.Host/Tgstation.Server.Host.csproj"
COPY . .
WORKDIR "/src/src/Tgstation.Server.Host"
RUN dotnet build "Tgstation.Server.Host.csproj" -c Debug -o /app/build
FROM build AS publish
RUN dotnet publish "Tgstation.Server.Host.csproj" -c Debug -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Tgstation.Server.Host.dll"]
+2 -1
View File
@@ -5,6 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host
{
@@ -29,7 +30,7 @@ namespace Tgstation.Server.Host
/// Entrypoint for the <see cref="Program"/>
/// </summary>
/// <param name="args">The command line arguments</param>
/// <returns>The <see cref="Process.ExitCode"/></returns>
/// <returns>The <see cref="global::System.Diagnostics.Process.ExitCode"/>.</returns>
public static async Task<int> Main(string[] args)
{
// first arg is 100% always the update path, starting it otherwise is solely for debugging purposes
@@ -1,12 +1,16 @@
{
"profiles": {
"Tgstation.Server.Host": {
"Local": {
"commandName": "Project",
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development"
"DOTNET_ENVIRONMENT": "Development",
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Docker": {
"commandName": "Docker",
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"publishAllPorts": true
}
}
}
+4 -2
View File
@@ -43,7 +43,9 @@ namespace Tgstation.Server.Host
throw new ArgumentNullException(nameof(args));
IHostBuilder CreateDefaultBuilder() => Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, configurationBuilder) => configurationBuilder.SetBasePath(IOManager.ResolvePath()))
.ConfigureAppConfiguration((context, configuration) => configuration
.SetBasePath(
IOManager.ResolvePath()))
.ConfigureServices(services => services.RemoveEventLogging());
var setupWizardHostBuilder = CreateDefaultBuilder()
@@ -56,7 +58,7 @@ namespace Tgstation.Server.Host
await setupHost.RunAsync(cancellationToken).ConfigureAwait(false);
}
var hostBuilder = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
var hostBuilder = CreateDefaultBuilder()
.ConfigureWebHostDefaults(webHostBuilder =>
webHostBuilder
.UseApplication(postSetupServices)
@@ -58,6 +58,8 @@ namespace Tgstation.Server.Host.Setup
if (services == null)
throw new ArgumentNullException(nameof(services));
services.RemoveEventLogging();
services.AddSingleton(IOManager);
services.AddSingleton(AssemblyInformationProvider);
@@ -1,7 +1,7 @@
using System;
using System.Threading;
namespace Tgstation.Server.Host
namespace Tgstation.Server.Host.System
{
/// <summary>
/// Contains a <see cref="CancellationToken"/> that triggers when the operating system requests the program shuts down.
@@ -26,6 +26,8 @@
<PropertyGroup>
<NodeModulesFolder>ClientApp/node_modules</NodeModulesFolder>
<NpmInstallStampFile>ClientApp/node_modules/.install-stamp</NpmInstallStampFile>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
</PropertyGroup>
<Target Name="NpmInstall" Inputs="ClientApp/package-lock.json" Outputs="$(NpmInstallStampFile)">
@@ -68,6 +70,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.9.10" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="Octokit" Version="0.47.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
@@ -9,26 +9,26 @@ using System.Threading.Tasks;
namespace Tgstation.Server.Host.Components.Repository.Tests
{
/// <summary>
/// Tests for <see cref="RepositoryFactory"/>.
/// Tests for <see cref="LibGit2RepositoryFactory"/>.
/// </summary>
[TestClass]
public sealed class TestRepositoryFactory
{
static IRepositoryFactory CreateFactory() => new RepositoryFactory(Mock.Of<ILogger<RepositoryFactory>>());
static ILibGit2RepositoryFactory CreateFactory() => new LibGit2RepositoryFactory(Mock.Of<ILogger<LibGit2RepositoryFactory>>());
static Task<LibGit2Sharp.IRepository> TestRepoLoading(
string path,
IRepositoryFactory repositoryFactory = null) =>
ILibGit2RepositoryFactory repositoryFactory = null) =>
(repositoryFactory ?? CreateFactory())
.CreateFromPath(path, default);
[TestMethod]
public void TestConstructionThrows() => Assert.ThrowsException<ArgumentNullException>(() => new RepositoryFactory(null));
public void TestConstructionThrows() => Assert.ThrowsException<ArgumentNullException>(() => new LibGit2RepositoryFactory(null));
[TestMethod]
public void TestInMemoryRepoCreation()
{
new RepositoryFactory(Mock.Of<ILogger<RepositoryFactory>>()).CreateInMemory().Dispose();
new LibGit2RepositoryFactory(Mock.Of<ILogger<LibGit2RepositoryFactory>>()).CreateInMemory().Dispose();
}
[TestMethod]