mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-23 13:07:07 +01:00
@@ -0,0 +1,10 @@
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "nuget"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- "Dependencies"
|
||||
open-pull-requests-limit: 100
|
||||
@@ -55,20 +55,32 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
if: github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id && github.event.pull_request.state == 'open'
|
||||
if: github.event_name == 'pull_request_target' && (github.event.pull_request.head.repo.id != github.event.pull_request.base.repo.id || github.event.pull_request.user.id == 49699333) && github.event.pull_request.state == 'open'
|
||||
steps:
|
||||
- name: Comment on new Fork PR
|
||||
if: github.event.action == 'opened' && !contains(github.event.pull_request.labels.*.name, 'CI Cleared')
|
||||
if: github.event.action == 'opened' && !contains(github.event.pull_request.labels.*.name, 'CI Cleared') && github.event.pull_request.user.id != 49699333
|
||||
uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308
|
||||
with:
|
||||
message: Thank you for contributing to ${{ github.event.pull_request.base.repo.name }}! The workflow '${{ github.workflow }}' requires repository secrets and will not run without approval. Maintainers can add the `CI Cleared` label to allow it to run. Please note that any changes to the workflow file will not be reflected in the run.
|
||||
|
||||
- name: Comment on dependabot PR
|
||||
if: github.event.action == 'opened' && !contains(github.event.pull_request.labels.*.name, 'CI Cleared') && github.event.pull_request.user.id == 49699333
|
||||
uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308
|
||||
with:
|
||||
message: Set the milestone to the next minor version, check for supply chain attacks, and then add the `CI Cleared` label to allow CI to run.
|
||||
|
||||
- name: "Remove Stale 'CI Cleared' Label"
|
||||
if: github.event.action == 'synchronize' || github.event.action == 'reopened'
|
||||
uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0
|
||||
with:
|
||||
labels: CI Cleared
|
||||
|
||||
- name: "Remove 'CI Approval Required' Label"
|
||||
if: (github.event.action == 'synchronize' || github.event.action == 'reopened') || ((github.event.action == 'opened' || github.event.action == 'labeled') && contains(github.event.pull_request.labels.*.name, 'CI Cleared'))
|
||||
uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0
|
||||
with:
|
||||
labels: CI Approval Required
|
||||
|
||||
- name: "Add 'CI Approval Required' Label"
|
||||
if: (github.event.action == 'synchronize' || github.event.action == 'reopened') || ((github.event.action == 'opened' || github.event.action == 'labeled') && !contains(github.event.pull_request.labels.*.name, 'CI Cleared'))
|
||||
uses: actions-ecosystem/action-add-labels@bd52874380e3909a1ac983768df6976535ece7f8
|
||||
@@ -76,13 +88,7 @@ jobs:
|
||||
labels: CI Approval Required
|
||||
github_token: ${{ github.token }}
|
||||
|
||||
- name: "Remove 'CI Approval Required' Label"
|
||||
if: (github.event.action == 'synchronize' || github.event.action == 'reopened') || ((github.event.action == 'opened' || github.event.action == 'labeled') && !contains(github.event.pull_request.labels.*.name, 'CI Cleared'))
|
||||
uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0
|
||||
with:
|
||||
labels: CI Approval Required
|
||||
|
||||
- name: Fail Clearance Check if PR has Unlabeled new Commits from Fork
|
||||
- name: Fail Clearance Check if PR has Unlabeled new Commits from User
|
||||
if: (github.event.action == 'synchronize' || github.event.action == 'reopened') || ((github.event.action == 'opened' || github.event.action == 'labeled') && !contains(github.event.pull_request.labels.*.name, 'CI Cleared'))
|
||||
run: exit 1
|
||||
|
||||
@@ -90,7 +96,7 @@ jobs:
|
||||
name: CI Start Gate
|
||||
needs: security-checkpoint
|
||||
runs-on: ubuntu-latest
|
||||
if: (!(cancelled() || failure()) && (needs.security-checkpoint.result == 'success' || (needs.security-checkpoint.result == 'skipped' && (github.event_name == 'push' || github.event_name == 'schedule' || (github.event.pull_request.head.repo.id == github.event.pull_request.base.repo.id && github.event_name != 'pull_request_target')))))
|
||||
if: (!(cancelled() || failure()) && (needs.security-checkpoint.result == 'success' || (needs.security-checkpoint.result == 'skipped' && (github.event_name == 'push' || github.event_name == 'schedule' || ((github.event.pull_request.head.repo.id == github.event.pull_request.base.repo.id && github.event.pull_request.user.id != 49699333) && github.event_name != 'pull_request_target')))))
|
||||
steps:
|
||||
- name: GitHub Requires at Least One Step for a Job
|
||||
run: exit 0
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<p align="center">
|
||||
<img src =./build/logo.svg>
|
||||
</p>
|
||||
|
||||
# tgstation-server
|
||||
|
||||
 [](https://codecov.io/gh/tgstation/tgstation-server)
|
||||
|
||||
+4
-4
@@ -3,12 +3,12 @@
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<Import Project="WebpanelVersion.props" />
|
||||
<PropertyGroup>
|
||||
<TgsCoreVersion>6.7.0</TgsCoreVersion>
|
||||
<TgsCoreVersion>6.8.0</TgsCoreVersion>
|
||||
<TgsConfigVersion>5.1.0</TgsConfigVersion>
|
||||
<TgsApiVersion>10.5.0</TgsApiVersion>
|
||||
<TgsApiVersion>10.6.0</TgsApiVersion>
|
||||
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
|
||||
<TgsApiLibraryVersion>13.5.0</TgsApiLibraryVersion>
|
||||
<TgsClientVersion>15.5.0</TgsClientVersion>
|
||||
<TgsApiLibraryVersion>13.6.0</TgsApiLibraryVersion>
|
||||
<TgsClientVersion>15.6.0</TgsClientVersion>
|
||||
<TgsDmapiVersion>7.1.3</TgsDmapiVersion>
|
||||
<TgsInteropVersion>5.9.0</TgsInteropVersion>
|
||||
<TgsHostWatchdogVersion>1.4.1</TgsHostWatchdogVersion>
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WixToolset.Dtf.CustomAction" Version="4.0.4" />
|
||||
<PackageReference Include="WixToolset.Dtf.CustomAction" Version="5.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -651,5 +651,11 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
[Description("Could not create dump as dotnet diagnostics threw an exception!")]
|
||||
DotnetDiagnosticsFailure,
|
||||
|
||||
/// <summary>
|
||||
/// The configured .dme could not be found.
|
||||
/// </summary>
|
||||
[Description("Could not load configured .dme due to it being outside the deployment directory! This should be a relative path.")]
|
||||
DeploymentWrongDme,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<!-- Usage: HTTP constants reference -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
|
||||
<!-- Usage: Decoding the 'nbf' property of JWTs -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.1" />
|
||||
<!-- Usage: Data model annotating -->
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -607,6 +607,9 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
else
|
||||
{
|
||||
var targetDme = ioManager.ConcatPath(outputDirectory, String.Join('.', job.DmeName, DmeExtension));
|
||||
if (!await ioManager.PathIsChildOf(outputDirectory, targetDme, cancellationToken))
|
||||
throw new JobException(ErrorCode.DeploymentWrongDme);
|
||||
|
||||
var targetDmeExists = await ioManager.FileExists(targetDme, cancellationToken);
|
||||
if (!targetDmeExists)
|
||||
throw new JobException(ErrorCode.DeploymentMissingDme);
|
||||
|
||||
@@ -103,8 +103,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
IInstanceManager instanceManager,
|
||||
IJobManager jobManager,
|
||||
IIOManager ioManager,
|
||||
IPortAllocator portAllocator,
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
IPortAllocator portAllocator,
|
||||
IPermissionsUpdateNotifyee permissionsUpdateNotifyee,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptions<SwarmConfiguration> swarmConfigurationOptions,
|
||||
@@ -150,77 +150,52 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (earlyOut != null)
|
||||
return earlyOut;
|
||||
|
||||
var unNormalizedPath = model.Path;
|
||||
var targetInstancePath = NormalizePath(unNormalizedPath);
|
||||
var targetInstancePath = NormalizePath(model.Path!);
|
||||
model.Path = targetInstancePath;
|
||||
|
||||
var installationDirectoryPath = NormalizePath(DefaultIOManager.CurrentDirectory);
|
||||
|
||||
bool InstanceIsChildOf(string otherPath)
|
||||
{
|
||||
if (!targetInstancePath.StartsWith(otherPath, StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
bool sameLength = targetInstancePath.Length == otherPath.Length;
|
||||
char dirSeparatorChar = targetInstancePath.ToCharArray()[Math.Min(otherPath.Length, targetInstancePath.Length - 1)];
|
||||
return sameLength
|
||||
|| dirSeparatorChar == Path.DirectorySeparatorChar
|
||||
|| dirSeparatorChar == Path.AltDirectorySeparatorChar;
|
||||
}
|
||||
|
||||
if (InstanceIsChildOf(installationDirectoryPath))
|
||||
var installationDirectoryPath = DefaultIOManager.CurrentDirectory;
|
||||
if (await ioManager.PathIsChildOf(installationDirectoryPath, targetInstancePath, cancellationToken))
|
||||
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtConflictingPath));
|
||||
|
||||
// Validate it's not a child of any other instance
|
||||
ulong countOfOtherInstances = 0;
|
||||
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
|
||||
{
|
||||
var newCancellationToken = cts.Token;
|
||||
try
|
||||
var instancePaths = await DatabaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.SwarmIdentifer == swarmConfiguration.Identifier)
|
||||
.Select(x => new Models.Instance
|
||||
{
|
||||
await DatabaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.SwarmIdentifer == swarmConfiguration.Identifier)
|
||||
.Select(x => new Models.Instance
|
||||
{
|
||||
Path = x.Path,
|
||||
})
|
||||
.ForEachAsync(
|
||||
otherInstance =>
|
||||
{
|
||||
if (++countOfOtherInstances >= generalConfiguration.InstanceLimit)
|
||||
earlyOut ??= Conflict(new ErrorMessageResponse(ErrorCode.InstanceLimitReached));
|
||||
else if (InstanceIsChildOf(otherInstance.Path!))
|
||||
earlyOut ??= Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtConflictingPath));
|
||||
Path = x.Path,
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (earlyOut != null && !newCancellationToken.IsCancellationRequested)
|
||||
cts.Cancel();
|
||||
},
|
||||
newCancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
if ((instancePaths.Count + 1) >= generalConfiguration.InstanceLimit)
|
||||
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceLimitReached));
|
||||
|
||||
if (earlyOut != null)
|
||||
return earlyOut;
|
||||
var instancePathChecks = instancePaths
|
||||
.Select(otherInstance => ioManager.PathIsChildOf(otherInstance.Path!, targetInstancePath, cancellationToken))
|
||||
.ToArray();
|
||||
|
||||
await Task.WhenAll(instancePathChecks);
|
||||
|
||||
if (instancePathChecks.Any(task => task.Result))
|
||||
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtConflictingPath));
|
||||
|
||||
// Last test, ensure it's in the list of valid paths
|
||||
if (!(generalConfiguration.ValidInstancePaths?
|
||||
.Select(path => NormalizePath(path))
|
||||
.Any(path => InstanceIsChildOf(path)) ?? true))
|
||||
var pathChecks = generalConfiguration.ValidInstancePaths?
|
||||
.Select(path => ioManager.PathIsChildOf(path, targetInstancePath, cancellationToken))
|
||||
.ToArray()
|
||||
?? Enumerable.Empty<Task<bool>>();
|
||||
await Task.WhenAll(pathChecks);
|
||||
if (!pathChecks.All(task => task.Result))
|
||||
return BadRequest(new ErrorMessageResponse(ErrorCode.InstanceNotAtWhitelistedPath));
|
||||
|
||||
async ValueTask<bool> DirExistsAndIsNotEmpty()
|
||||
{
|
||||
if (!await ioManager.DirectoryExists(model.Path, cancellationToken))
|
||||
if (!await ioManager.DirectoryExists(targetInstancePath, cancellationToken))
|
||||
return false;
|
||||
|
||||
var filesTask = ioManager.GetFiles(model.Path, cancellationToken);
|
||||
var dirsTask = ioManager.GetDirectories(model.Path, cancellationToken);
|
||||
var filesTask = ioManager.GetFiles(targetInstancePath, cancellationToken);
|
||||
var dirsTask = ioManager.GetDirectories(targetInstancePath, cancellationToken);
|
||||
|
||||
var files = await filesTask;
|
||||
var dirs = await dirsTask;
|
||||
@@ -230,8 +205,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
var dirExistsTask = DirExistsAndIsNotEmpty();
|
||||
bool attached = false;
|
||||
if (await ioManager.FileExists(model.Path, cancellationToken) || await dirExistsTask)
|
||||
if (!await ioManager.FileExists(ioManager.ConcatPath(model.Path, InstanceAttachFileName), cancellationToken))
|
||||
if (await ioManager.FileExists(targetInstancePath, cancellationToken) || await dirExistsTask)
|
||||
if (!await ioManager.FileExists(ioManager.ConcatPath(targetInstancePath, InstanceAttachFileName), cancellationToken))
|
||||
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtExistingPath));
|
||||
else
|
||||
attached = true;
|
||||
@@ -248,7 +223,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
try
|
||||
{
|
||||
// actually reserve it now
|
||||
await ioManager.CreateDirectory(unNormalizedPath, cancellationToken);
|
||||
await ioManager.CreateDirectory(targetInstancePath, cancellationToken);
|
||||
await ioManager.DeleteFile(ioManager.ConcatPath(targetInstancePath, InstanceAttachFileName), cancellationToken);
|
||||
}
|
||||
catch
|
||||
@@ -397,13 +372,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
}
|
||||
|
||||
string? originalModelPath = null;
|
||||
string? rawPath = null;
|
||||
string? normalizedPath = null;
|
||||
var originalOnline = originalModel.Online!.Value;
|
||||
if (model.Path != null)
|
||||
{
|
||||
rawPath = NormalizePath(model.Path);
|
||||
normalizedPath = NormalizePath(model.Path);
|
||||
|
||||
if (rawPath != originalModel.Path)
|
||||
if (normalizedPath != originalModel.Path)
|
||||
{
|
||||
if (!userRights.HasFlag(InstanceManagerRights.Relocate))
|
||||
return Forbid();
|
||||
@@ -415,7 +390,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
return Conflict(new ErrorMessageResponse(ErrorCode.InstanceAtExistingPath));
|
||||
|
||||
originalModelPath = originalModel.Path;
|
||||
originalModel.Path = rawPath;
|
||||
originalModel.Path = normalizedPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,7 +480,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
var moving = originalModelPath != null;
|
||||
if (moving)
|
||||
{
|
||||
var description = $"Move instance ID {originalModel.Id} from {originalModelPath} to {rawPath}";
|
||||
var description = $"Move instance ID {originalModel.Id} from {originalModelPath} to {normalizedPath}";
|
||||
var job = Job.Create(JobCode.Move, AuthenticationContext.User, originalModel, InstanceManagerRights.Relocate);
|
||||
job.Description = description;
|
||||
|
||||
@@ -823,8 +798,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
return null;
|
||||
|
||||
path = ioManager.ResolvePath(path);
|
||||
if (platformIdentifier.IsWindows)
|
||||
path = path.ToUpperInvariant().Replace('\\', '/');
|
||||
path = platformIdentifier.NormalizePath(path);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,11 @@ namespace Tgstation.Server.Host.Database
|
||||
/// </summary>
|
||||
readonly DatabaseConfiguration databaseConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SwarmConfiguration"/> for the <see cref="DatabaseSeeder"/>.
|
||||
/// </summary>
|
||||
readonly SwarmConfiguration swarmConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// Add a default system <see cref="User"/> to a given <paramref name="databaseContext"/>.
|
||||
/// </summary>
|
||||
@@ -83,6 +88,7 @@ namespace Tgstation.Server.Host.Database
|
||||
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="databaseConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="databaseConfiguration"/>.</param>
|
||||
/// <param name="swarmConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="swarmConfiguration"/>.</param>
|
||||
/// <param name="databaseLogger">The value of <see cref="databaseLogger"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public DatabaseSeeder(
|
||||
@@ -90,6 +96,7 @@ namespace Tgstation.Server.Host.Database
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptions<DatabaseConfiguration> databaseConfigurationOptions,
|
||||
IOptions<SwarmConfiguration> swarmConfigurationOptions,
|
||||
ILogger<DatabaseContext> databaseLogger,
|
||||
ILogger<DatabaseSeeder> logger)
|
||||
{
|
||||
@@ -97,6 +104,7 @@ namespace Tgstation.Server.Host.Database
|
||||
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
|
||||
databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions));
|
||||
this.databaseLogger = databaseLogger ?? throw new ArgumentNullException(nameof(databaseLogger));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
@@ -223,16 +231,14 @@ namespace Tgstation.Server.Host.Database
|
||||
}
|
||||
}
|
||||
|
||||
if (platformIdentifier.IsWindows)
|
||||
{
|
||||
// normalize backslashes to forward slashes
|
||||
var allInstances = await databaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.ToListAsync(cancellationToken);
|
||||
foreach (var instance in allInstances)
|
||||
instance.Path = instance.Path!.Replace('\\', '/');
|
||||
}
|
||||
// normalize backslashes to forward slashes
|
||||
var allInstances = await databaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(instance => instance.SwarmIdentifer == swarmConfiguration.Identifier)
|
||||
.ToListAsync(cancellationToken);
|
||||
foreach (var instance in allInstances)
|
||||
instance.Path = platformIdentifier.NormalizePath(instance.Path!.Replace('\\', '/'));
|
||||
|
||||
if (generalConfiguration.ByondTopicTimeout != 0)
|
||||
{
|
||||
|
||||
@@ -358,6 +358,33 @@ namespace Tgstation.Server.Host.IO
|
||||
DefaultBufferSize,
|
||||
true);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> PathIsChildOf(string parentPath, string childPath, CancellationToken cancellationToken) => Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
parentPath = ResolvePath(parentPath);
|
||||
childPath = ResolvePath(childPath);
|
||||
|
||||
if (parentPath == childPath)
|
||||
return true;
|
||||
|
||||
// https://stackoverflow.com/questions/5617320/given-full-path-check-if-path-is-subdirectory-of-some-other-path-or-otherwise?lq=1
|
||||
var di1 = new DirectoryInfo(parentPath);
|
||||
var di2 = new DirectoryInfo(childPath);
|
||||
while (di2.Parent != null)
|
||||
{
|
||||
if (di2.Parent.FullName == di1.FullName)
|
||||
return true;
|
||||
|
||||
di2 = di2.Parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
cancellationToken,
|
||||
BlockingTaskCreationOptions,
|
||||
TaskScheduler.Current);
|
||||
|
||||
/// <summary>
|
||||
/// Copies a directory from <paramref name="src"/> to <paramref name="dest"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -45,6 +45,15 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <returns><see langword="true"/> if <paramref name="path"/> contains a '..' accessor, <see langword="false"/> otherwise.</returns>
|
||||
bool PathContainsParentAccess(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a given <paramref name="parentPath"/> is a parent of a given <paramref name="parentPath"/>.
|
||||
/// </summary>
|
||||
/// <param name="parentPath">The parent path.</param>
|
||||
/// <param name="childPath">The child path.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if <paramref name="childPath"/> is a child of <paramref name="parentPath"/> or they are equivalent.</returns>
|
||||
Task<bool> PathIsChildOf(string parentPath, string childPath, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Copies a directory from <paramref name="src"/> to <paramref name="dest"/>.
|
||||
/// </summary>
|
||||
@@ -68,7 +77,7 @@ namespace Tgstation.Server.Host.IO
|
||||
/// </summary>
|
||||
/// <param name="path">The file to check for existence.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> resulting in <see langword="true"/> if the file at <paramref name="path"/> exists, <see langword="false"/> otherwise.</returns>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the file at <paramref name="path"/> exists, <see langword="false"/> otherwise.</returns>
|
||||
Task<bool> FileExists(string path, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -17,5 +17,12 @@ namespace Tgstation.Server.Host.System
|
||||
/// The extension of executable script files for the system.
|
||||
/// </summary>
|
||||
string ScriptFileExtension { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Normalize a path for consistency.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to normalize.</param>
|
||||
/// <returns>The normalized path.</returns>
|
||||
string NormalizePath(string path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Tgstation.Server.Host.System
|
||||
@@ -21,5 +22,15 @@ namespace Tgstation.Server.Host.System
|
||||
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||
ScriptFileExtension = IsWindows ? "bat" : "sh";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string NormalizePath(string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(path);
|
||||
if (IsWindows)
|
||||
path = path.Replace('\\', '/');
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
<!-- Usage: Temporary resolution to compatibility issues with EFCore 7 and .NET 8 -->
|
||||
<PackageReference Include="System.Security.Permissions" Version="8.0.0" />
|
||||
<!-- Usage: .DeleteAsync() support for IQueryable<T>s -->
|
||||
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="8.103.0" />
|
||||
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="8.103.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: Creating mock database implementations -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -200,6 +202,27 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
deployJob = await dreamMakerClient.Compile(cancellationToken);
|
||||
await WaitForJob(deployJob, 40, true, ErrorCode.DeploymentMissingDme, cancellationToken);
|
||||
|
||||
// set to an absolute path that does exist
|
||||
var tempFile = Path.GetTempFileName().Replace('\\', '/');
|
||||
try
|
||||
{
|
||||
// for testing purposes, assume same drive for windows
|
||||
var relativePath = $"../../{String.Join("/", instanceClient.Metadata.Path.Replace('\\', '/').Where(pathChar => pathChar == '/').Select(x => ".."))}{tempFile.Substring(tempFile.IndexOf('/'))}";
|
||||
var dmePath = $"{tempFile}.dme";
|
||||
File.Move(tempFile, dmePath);
|
||||
tempFile = dmePath;
|
||||
await dreamMakerClient.Update(new DreamMakerRequest
|
||||
{
|
||||
ProjectName = relativePath
|
||||
}, cancellationToken);
|
||||
deployJob = await dreamMakerClient.Compile(cancellationToken);
|
||||
await WaitForJob(deployJob, 40, true, ErrorCode.DeploymentWrongDme, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
|
||||
// check that we can change the visibility
|
||||
|
||||
await vpTest;
|
||||
|
||||
Reference in New Issue
Block a user