mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-22 04:27:19 +01:00
Merge branch 'dev' into TheGreatAuthenticationRework
This commit is contained in:
@@ -50,6 +50,9 @@ You must also have the following environment variables set. To run them more acc
|
||||
- `TGS_TEST_DATABASE_TYPE`: `MySql`, `MariaDB`, `PostgresSql`, or `SqlServer`.
|
||||
- `TGS_TEST_CONNECTION_STRING`: To a valid database connection string. You can use the setup wizard to create one.
|
||||
- (Optional) `TGS_TEST_GITHUB_TOKEN`: A GitHub personal access token with no scopes used to bypass rate limits.
|
||||
- (Optional) `TGS_TEST_BYOND_ZIP_DOWNLOAD_TEMPLATE`: Template URL for downloading BYOND zip files from a non-official mirror.
|
||||
- (Optional) `TGS_TEST_BYOND_MIRROR_VERSION_TXT`: version.txt for a BYOND zip mirror. Requires `TGS_TEST_BYOND_ZIP_DOWNLOAD_TEMPLATE`.
|
||||
- (Optional) `TGS_TEST_BYOND_ZIPS_BASE_PATH`: Directory on disk to cache BYOND zip files.
|
||||
- (Optional) The following variables are all interdependent, so if one is set they all must be.
|
||||
- `TGS_TEST_DISCORD_TOKEN`: To a valid discord bot token.
|
||||
- `TGS_TEST_DISCORD_CHANNEL`: To a valid discord channel ID that the above bot can access.
|
||||
|
||||
@@ -42,6 +42,9 @@ env:
|
||||
TGS_NODE_VERSION: 20.x
|
||||
TGS_TEST_GITHUB_TOKEN: ${{ secrets.LIVE_TESTS_TOKEN }}
|
||||
PACKAGING_PRIVATE_KEY_PASSPHRASE: ${{ secrets.PACKAGING_PRIVATE_KEY_PASSPHRASE }}
|
||||
TGS_TEST_BYOND_ZIP_DOWNLOAD_TEMPLATE: https://mocha.affectedarc07.co.uk/tgs_byond_mirrors/${Major}.${Minor}_byond${Linux:_linux}.zip
|
||||
TGS_TEST_BYOND_MIRROR_VERSION_TXT: https://mocha.affectedarc07.co.uk/tgs_byond_mirrors/version.txt
|
||||
DMAPI_BYOND_BUILD_MIRROR_DOWNLOAD_TEMPLATE: https://mocha.affectedarc07.co.uk/tgs_byond_mirrors/${FULL_VERSION}_byond_linux.zip # Available vars are $BYOND_MAJOR (i.e. 516) and $FULL_VERSION (i.e. 516.1666)
|
||||
|
||||
concurrency:
|
||||
group: "ci-${{ (github.event_name != 'push' && github.event_name != 'schedule' && github.event.inputs.pull_request_number) || github.run_id }}-${{ github.event_name }}"
|
||||
@@ -90,6 +93,56 @@ jobs:
|
||||
name: release_notes_bins
|
||||
path: ./release_notes_bins/
|
||||
|
||||
validate-nix-flake:
|
||||
name: Validate Nix Flake
|
||||
needs: start-gate
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TEST_TGS_VERSION: "6.17.0" # Version we use here doesn't matter as it won't be executed. Just used to download a zip and calc hash
|
||||
steps:
|
||||
- name: Install Native Packages # Name checked in rerunFlakyTests.js
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y xmlstarlet
|
||||
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
- name: Checkout (Branch)
|
||||
uses: actions/checkout@v4
|
||||
if: github.event_name == 'push' || github.event_name == 'schedule'
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v4
|
||||
if: github.event_name != 'push' && github.event_name != 'schedule'
|
||||
with:
|
||||
ref: "refs/pull/${{ inputs.pull_request_number }}/merge"
|
||||
|
||||
- name: Replace current TGS version with test version
|
||||
run: |
|
||||
CURRENT_TGS_VERSION="$(xmlstarlet sel -N X="http://schemas.microsoft.com/developer/msbuild/2003" --template --value-of /X:Project/X:PropertyGroup/X:TgsCoreVersion build/Version.props)"
|
||||
sed -i -e "s/<TgsCoreVersion>${CURRENT_TGS_VERSION}/<TgsCoreVersion>${{ env.TEST_TGS_VERSION }}/g" build/Version.props
|
||||
|
||||
- name: Retrieve ServerConsole.zip Artifact
|
||||
run: |
|
||||
mkdir release
|
||||
curl -L https://github.com/tgstation/tgstation-server/releases/download/tgstation-server-v${{ env.TEST_TGS_VERSION }}/ServerConsole.zip -f -o release/ServerConsole.zip
|
||||
|
||||
- name: Regenerate Nix Hash
|
||||
run: |
|
||||
nix hash path ./release > build/package/nix/ServerConsole.sha256
|
||||
cat build/package/nix/ServerConsole.sha256
|
||||
|
||||
- name: Cleanup Download
|
||||
run: rm -rf ./release
|
||||
|
||||
- name: Check Flake
|
||||
run: |
|
||||
cd build/package/nix
|
||||
nix flake check
|
||||
|
||||
code-scanning:
|
||||
name: Run CodeQL
|
||||
needs: start-gate
|
||||
@@ -164,21 +217,14 @@ jobs:
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 libgcc-s1:i386
|
||||
sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 libgcc-s1:i386 libcurl4:i386
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
id: cache-byond
|
||||
with:
|
||||
path: ~/byond-zips-cache
|
||||
key: byond-zips
|
||||
|
||||
- name: Setup BYOND Cache if Necessary and Install
|
||||
- name: Evaluate BYOND version
|
||||
id: version_evaluation
|
||||
run: |
|
||||
echo "Setting up BYOND."
|
||||
FULL_VERSION=${{ matrix.byond }}
|
||||
if [[ "$FULL_VERSION" = "EDGE" ]] ; then
|
||||
VERSIONS=$(curl https://www.byond.com/download/version.txt)
|
||||
VERSIONS=$(curl https://spacestation13.github.io/byond-builds/version.txt)
|
||||
FULL_VERSION=$(echo "$VERSIONS" | tail -n1)
|
||||
echo "EDGE version evaluated to $FULL_VERSION"
|
||||
|
||||
@@ -190,14 +236,31 @@ jobs:
|
||||
FULL_VERSION=${bad_linux_releases[$FULL_VERSION]}
|
||||
fi
|
||||
fi
|
||||
if [[ ! -f $HOME/byond-zips-cache/linux/$FULL_VERSION.zip ]] ; then
|
||||
echo "EVALUATED_VERSION=$FULL_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
id: cache-byond
|
||||
with:
|
||||
path: ~/byond-zips-cache/linux/${{ steps.version_evaluation.outputs.EVALUATED_VERSION }}
|
||||
key: byond-zips-linux-${{ steps.version_evaluation.outputs.EVALUATED_VERSION }}
|
||||
|
||||
- name: Setup BYOND Cache if Necessary and Install
|
||||
run: |
|
||||
echo "Setting up BYOND."
|
||||
FULL_VERSION=${{ steps.version_evaluation.outputs.EVALUATED_VERSION }}
|
||||
if [[ ! -f $HOME/byond-zips-cache/linux/$FULL_VERSION/$FULL_VERSION.zip ]] ; then
|
||||
BYOND_MAJOR=${FULL_VERSION%.*}
|
||||
mkdir -p $HOME/byond-zips-cache/linux
|
||||
curl "https://www.byond.com/download/build/$BYOND_MAJOR/${FULL_VERSION}_byond_linux.zip" -o $HOME/byond-zips-cache/linux/$FULL_VERSION.zip
|
||||
mkdir -p $HOME/byond-zips-cache/linux/$FULL_VERSION
|
||||
DOWNLOAD_URL="${{ env.DMAPI_BYOND_BUILD_MIRROR_DOWNLOAD_TEMPLATE }}"
|
||||
if [[ "${{ matrix.byond }}" = "EDGE" ]] ; then
|
||||
DOWNLOAD_URL="https://spacestation13.github.io/byond-builds/$BYOND_MAJOR/${FULL_VERSION}_byond_linux.zip"
|
||||
fi
|
||||
curl "$DOWNLOAD_URL" -o $HOME/byond-zips-cache/linux/$FULL_VERSION/$FULL_VERSION.zip
|
||||
fi
|
||||
mkdir -p "$HOME/BYOND"
|
||||
cd "$HOME/BYOND"
|
||||
cp $HOME/byond-zips-cache/linux/$FULL_VERSION.zip byond.zip
|
||||
cp $HOME/byond-zips-cache/linux/$FULL_VERSION/$FULL_VERSION.zip byond.zip
|
||||
unzip byond.zip
|
||||
cd byond
|
||||
make here
|
||||
@@ -509,13 +572,6 @@ jobs:
|
||||
if: always()
|
||||
run: rm -f ${{ env.TGS_TELEMETRY_KEY_FILE }}
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
id: cache-byond
|
||||
with:
|
||||
path: ~/byond-zips-cache
|
||||
key: byond-zips
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: sudo dotnet test --no-build --logger "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true" --filter TestCategory!=RequiresDatabase -c ${{ matrix.configuration }}NoWindows --collect:"XPlat Code Coverage" --settings build/ci.runsettings --results-directory ./TestResults tgstation-server.sln
|
||||
env:
|
||||
@@ -578,13 +634,6 @@ jobs:
|
||||
if: always()
|
||||
run: rm -f ${{ env.TGS_TELEMETRY_KEY_FILE }}
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
id: cache-byond
|
||||
with:
|
||||
path: ~/byond-zips-cache
|
||||
key: byond-zips
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: dotnet test --no-build --logger "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true" --filter TestCategory!=RequiresDatabase -c ${{ matrix.configuration }}NoWix --collect:"XPlat Code Coverage" --settings build/ci.runsettings --results-directory ./TestResults tgstation-server.sln
|
||||
env:
|
||||
@@ -598,9 +647,50 @@ jobs:
|
||||
name: windows-unit-test-coverage-${{ matrix.configuration }}
|
||||
path: ./TestResults/
|
||||
|
||||
prep-edge-versions:
|
||||
name: Prepare Live Tests Cache of EDGE Versions
|
||||
needs: start-gate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cache BYOND .zips (Linux)
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/byond-zips-cache/live/linux
|
||||
key: byond-zips-linux-live
|
||||
|
||||
- name: Cache BYOND .zips (Windows)
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/byond-zips-cache/live/windows
|
||||
key: byond-zips-windows-live
|
||||
|
||||
- name: Evaluate BYOND version
|
||||
id: version_evaluation
|
||||
run: |
|
||||
VERSIONS=$(curl https://spacestation13.github.io/byond-builds/version.txt)
|
||||
FULL_VERSION=$(echo "$VERSIONS" | tail -n1)
|
||||
echo "EDGE version evaluated to $FULL_VERSION"
|
||||
echo "EVALUATED_VERSION=$FULL_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup BYOND Cache if Necessary and Install
|
||||
run: |
|
||||
echo "Downloading BYOND."
|
||||
FULL_VERSION=${{ steps.version_evaluation.outputs.EVALUATED_VERSION }}
|
||||
BYOND_MAJOR=${FULL_VERSION%.*}
|
||||
if [[ ! -f $HOME/byond-zips-cache/live/linux/$FULL_VERSION/$FULL_VERSION.zip ]] ; then
|
||||
mkdir -p $HOME/byond-zips-cache/live/linux/$FULL_VERSION
|
||||
DOWNLOAD_URL="https://spacestation13.github.io/byond-builds/$BYOND_MAJOR/${FULL_VERSION}_byond_linux.zip"
|
||||
curl "$DOWNLOAD_URL" -o $HOME/byond-zips-cache/live/linux/$FULL_VERSION/$FULL_VERSION.zip
|
||||
fi
|
||||
if [[ ! -f $HOME/byond-zips-cache/live/windows/$FULL_VERSION/$FULL_VERSION.zip ]] ; then
|
||||
mkdir -p $HOME/byond-zips-cache/live/windows/$FULL_VERSION
|
||||
DOWNLOAD_URL="https://spacestation13.github.io/byond-builds/$BYOND_MAJOR/${FULL_VERSION}_byond.zip"
|
||||
curl "$DOWNLOAD_URL" -o $HOME/byond-zips-cache/live/windows/$FULL_VERSION/$FULL_VERSION.zip
|
||||
fi
|
||||
|
||||
windows-integration-tests:
|
||||
name: Windows Live Tests
|
||||
needs: [dmapi-build, opendream-build]
|
||||
needs: [dmapi-build, opendream-build, prep-edge-versions]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -633,6 +723,12 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ env.TGS_NODE_VERSION }}
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/byond-zips-cache/live/windows
|
||||
key: byond-zips-windows-live
|
||||
|
||||
- name: Set TGS_TEST_DUMP_API_SPEC
|
||||
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Advanced' && matrix.database-type == 'SqlServer' }}
|
||||
run: echo "TGS_TEST_DUMP_API_SPEC=yes" >> $Env:GITHUB_ENV
|
||||
@@ -717,13 +813,6 @@ jobs:
|
||||
if: always()
|
||||
run: rm -f ${{ env.TGS_TELEMETRY_KEY_FILE }}
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
id: cache-byond
|
||||
with:
|
||||
path: ~/byond-zips-cache
|
||||
key: byond-zips
|
||||
|
||||
- name: Run Live Tests # Logging here is weird because printing massive amounts of text on Windows runners is SLOW AS SHIT!!!
|
||||
id: live-tests
|
||||
shell: bash
|
||||
@@ -816,7 +905,7 @@ jobs:
|
||||
|
||||
linux-integration-tests:
|
||||
name: Linux Live Tests
|
||||
needs: [dmapi-build, opendream-build]
|
||||
needs: [dmapi-build, opendream-build, prep-edge-versions]
|
||||
services: # We start all dbs here so we can just code the stuff once
|
||||
mssql:
|
||||
image: ${{ (matrix.database-type == 'SqlServer') && 'mcr.microsoft.com/mssql/server:2019-latest' || '' }}
|
||||
@@ -884,13 +973,19 @@ jobs:
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 gdb libgcc-s1:i386 libgdiplus
|
||||
sudo apt-get install -y -o APT::Immediate-Configure=0 libc6-i386 libstdc++6:i386 gdb libgcc-s1:i386 libgdiplus libcurl4:i386
|
||||
|
||||
- name: Setup Node.JS
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ env.TGS_NODE_VERSION }}
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/byond-zips-cache/live/linux
|
||||
key: byond-zips-linux-live
|
||||
|
||||
- name: Set Sqlite Connection Info
|
||||
if: ${{ matrix.database-type == 'Sqlite' }}
|
||||
run: |
|
||||
@@ -945,13 +1040,6 @@ jobs:
|
||||
if: always()
|
||||
run: rm -f ${{ env.TGS_TELEMETRY_KEY_FILE }}
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v4
|
||||
id: cache-byond
|
||||
with:
|
||||
path: ~/byond-zips-cache
|
||||
key: byond-zips
|
||||
|
||||
- name: Run Live Tests
|
||||
run: |
|
||||
cd tests/Tgstation.Server.Tests
|
||||
@@ -1657,6 +1745,7 @@ jobs:
|
||||
check-winget-pr-template,
|
||||
efcore-version-match,
|
||||
code-scanning,
|
||||
validate-nix-flake,
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
sudo apt-get install -y xmlstarlet
|
||||
|
||||
- name: Setup Nix
|
||||
uses: cachix/install-nix-action@v30
|
||||
uses: cachix/install-nix-action@v31
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
|
||||
@@ -639,7 +639,7 @@ Bots have a set of built-in commands that can be triggered via `!tgs`, mentionin
|
||||
|
||||
All files in game code deployments are considered transient by default, meaning when new code is deployed, changes will be lost. Static files allow you to specify which files and folders stick around throughout all deployments.
|
||||
|
||||
The `StaticFiles` folder contains 3 root folders which cannot be deleted and operate under special rules
|
||||
The `Configuration` folder contains 3 root folders which cannot be deleted and operate under special rules
|
||||
- `CodeModifications`
|
||||
- `EventScripts`
|
||||
- `GameStaticFiles`
|
||||
|
||||
+3
-1
@@ -59,11 +59,13 @@ RUN export TGS_TELEMETRY_KEY_FILE="../../${TGS_TELEMETRY_KEY_FILE}" \
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0-bookworm-slim
|
||||
|
||||
#needed for byond, curl for healthchecks
|
||||
RUN apt-get update \
|
||||
RUN dpkg --add-architecture i386 \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y \
|
||||
gcc-multilib \
|
||||
gdb \
|
||||
curl \
|
||||
libcurl4:i386 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<!-- Usage: Hard to say what exactly this is for, but not including it removes the test icon and breaks vstest.console.exe for some reason -->
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" Condition="'$(TgsTestNoSdk)' != 'true'" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" Condition="'$(TgsTestNoSdk)' != 'true'" />
|
||||
<!-- Usage: Dependency mocking for tests -->
|
||||
<!-- Pinned: Be VERY careful about updating https://github.com/moq/moq/issues/1372 -->
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<!-- Usage: MSTest execution -->
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.8.3" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.9.3" />
|
||||
<!-- Usage: MSTest asserts etc... -->
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.8.3" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.9.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<Import Project="WebpanelVersion.props" />
|
||||
<PropertyGroup>
|
||||
<TgsCoreVersion>6.17.0</TgsCoreVersion>
|
||||
<TgsConfigVersion>5.7.0</TgsConfigVersion>
|
||||
<TgsCoreVersion>6.18.0</TgsCoreVersion>
|
||||
<TgsConfigVersion>5.8.0</TgsConfigVersion>
|
||||
<TgsRestVersion>10.13.0</TgsRestVersion>
|
||||
<TgsGraphQLVersion>0.6.0</TgsGraphQLVersion>
|
||||
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
|
||||
|
||||
@@ -22,11 +22,6 @@ apt-get install -y \
|
||||
xmlstarlet \
|
||||
libgdiplus
|
||||
|
||||
declare repo_version=$(if command -v lsb_release &> /dev/null; then lsb_release -r -s; else grep -oP '(?<=^VERSION_ID=).+' /etc/os-release | tr -d '"'; fi)
|
||||
curl -L https://packages.microsoft.com/config/ubuntu/$repo_version/packages-microsoft-prod.deb -o packages-microsoft-prod.deb
|
||||
dpkg -i ./packages-microsoft-prod.deb
|
||||
rm packages-microsoft-prod.deb
|
||||
|
||||
# https://github.com/nodesource/distributions
|
||||
mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
|
||||
@@ -21,6 +21,7 @@ Depends:
|
||||
libstdc++6:i386 [amd64],
|
||||
libstdc++6 [i386],
|
||||
gcc-multilib [amd64],
|
||||
libcurl4 [i386],
|
||||
Recommends:
|
||||
libsystemd0,
|
||||
gdb,
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
|
||||
inputs = {};
|
||||
|
||||
outputs = { ... }: {
|
||||
outputs = { nixpkgs, ... }: {
|
||||
nixosModules = {
|
||||
default = { ... }: {
|
||||
imports = [ ./tgstation-server.nix ];
|
||||
};
|
||||
};
|
||||
checks.x86_64-linux.package-build = nixpkgs.legacyPackages.x86_64-linux.callPackage ./package.nix { };
|
||||
};
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ stdenv.mkDerivation {
|
||||
gcc_multi
|
||||
glibc
|
||||
bash
|
||||
curl
|
||||
];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
makeWrapper
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<!-- Usage: HTTP constants reference -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.3.0" />
|
||||
<!-- Usage: Decoding the 'nbf' property of JWTs -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.9.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.13.0" />
|
||||
<!-- Usage: Data model annotating -->
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- GraphQL connector and code generator -->
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
|
||||
<PackageReference Include="StrawberryShake.Server" Version="15.1.3" />
|
||||
<PackageReference Include="StrawberryShake.Server" Version="15.1.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: Connecting to SignalR hubs in API -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.7" />
|
||||
<!-- Usage: Using target JSON serializer for API -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: Identifying if we're running under SystemD -->
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="9.0.7" />
|
||||
<!-- Usage: Console logging plugin -->
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -21,21 +21,21 @@
|
||||
<!-- Usage: Command line argument support -->
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
|
||||
<!-- Usage: Identifies when we are running in the context of the Windows SCM -->
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.7" />
|
||||
<!-- Usage: Windows event log logging plugin -->
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="9.0.7" />
|
||||
<!-- Usage: Console logging plugin -->
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.7" />
|
||||
<!-- Usage: Updated transitive dependency of Core.System.ServiceProcess -->
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<!-- Usage: Updated transitive dependency of Core.System.ServiceProcess -->
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.4" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.7" />
|
||||
<!-- Usage: Updated transitive dependency, unable to tell what of -->
|
||||
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||
<!-- Usage: OS identification -->
|
||||
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
|
||||
<!-- Usage: Windows Service Manager intergration -->
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="9.0.4" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+2
-1
@@ -38,7 +38,8 @@
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StrawberryShake.Server" Version="15.1.3" />
|
||||
<!-- GraphQL code generator -->
|
||||
<PackageReference Include="StrawberryShake.Server" Version="15.1.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1855,14 +1855,15 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"form-data@npm:^4.0.0":
|
||||
version: 4.0.2
|
||||
resolution: "form-data@npm:4.0.2"
|
||||
version: 4.0.4
|
||||
resolution: "form-data@npm:4.0.4"
|
||||
dependencies:
|
||||
asynckit: "npm:^0.4.0"
|
||||
combined-stream: "npm:^1.0.8"
|
||||
es-set-tostringtag: "npm:^2.1.0"
|
||||
hasown: "npm:^2.0.2"
|
||||
mime-types: "npm:^2.1.12"
|
||||
checksum: 10/82c65b426af4a40090e517a1bc9057f76970b4c6043e37aa49859c447d88553e77d4cc5626395079a53d2b0889ba5f2a49f3900db3ad3f3f1bf76613532572fb
|
||||
checksum: 10/a4b62e21932f48702bc468cc26fb276d186e6b07b557e3dd7cc455872bdbb82db7db066844a64ad3cf40eaf3a753c830538183570462d3649fdfd705601cbcfb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: Logging abstractions -->
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.7" />
|
||||
<!-- Usage: POSIX support for signals -->
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "9.0.4",
|
||||
"version": "9.0.7",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
|
||||
@@ -351,28 +351,11 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
|
||||
/// <inheritdoc />
|
||||
public void QueueWatchdogMessage(string message)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(message);
|
||||
=> QueueMessageGeneric(mapping => mapping.IsWatchdogChannel, message, "WD");
|
||||
|
||||
message = String.Format(CultureInfo.InvariantCulture, "WD: {0}", message);
|
||||
|
||||
if (!initialProviderConnectionsTask!.IsCompleted)
|
||||
logger.LogTrace("Waiting for initial provider connections before sending watchdog message...");
|
||||
|
||||
// Reimplementing QueueMessage
|
||||
QueueMessageInternal(
|
||||
new MessageContent
|
||||
{
|
||||
Text = message,
|
||||
},
|
||||
() =>
|
||||
{
|
||||
// so it doesn't change while we're using it
|
||||
lock (mappedChannels)
|
||||
return mappedChannels.Where(x => x.Value.IsWatchdogChannel).Select(x => x.Key).ToList();
|
||||
},
|
||||
true);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public void QueueRawDeploymentMessage(string message)
|
||||
=> QueueMessageGeneric(mapping => mapping.IsUpdatesChannel, message, null);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<string?, string, Action<bool>> QueueDeploymentMessage(
|
||||
@@ -1111,5 +1094,38 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
|
||||
AddMessageTask(SendMessageTask());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues a message to a selected set of <see cref="ChannelMapping"/>s.
|
||||
/// </summary>
|
||||
/// <param name="channelSelector">A <see cref="Predicate{T}"/> for selecting the <see cref="ChannelMapping"/>s to send to.</param>
|
||||
/// <param name="message">The message to send.</param>
|
||||
/// <param name="prefix">The optional prefix to the message to be sent.</param>
|
||||
void QueueMessageGeneric(Predicate<ChannelMapping> channelSelector, string message, string? prefix)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(message);
|
||||
|
||||
if (prefix != null)
|
||||
{
|
||||
message = $"{prefix}: {message}";
|
||||
}
|
||||
|
||||
if (!initialProviderConnectionsTask!.IsCompleted)
|
||||
logger.LogTrace("Waiting for initial provider connections before sending chat message...");
|
||||
|
||||
// Reimplementing QueueMessage
|
||||
QueueMessageInternal(
|
||||
new MessageContent
|
||||
{
|
||||
Text = message,
|
||||
},
|
||||
() =>
|
||||
{
|
||||
// so it doesn't change while we're using it
|
||||
lock (mappedChannels)
|
||||
return mappedChannels.Where(x => channelSelector(x.Value)).Select(x => x.Key).ToList();
|
||||
},
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,12 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
/// <param name="message">The message being sent.</param>
|
||||
void QueueWatchdogMessage(string message);
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat <paramref name="message"/> to configured deployment channels.
|
||||
/// </summary>
|
||||
/// <param name="message">The message being sent.</param>
|
||||
void QueueRawDeploymentMessage(string message);
|
||||
|
||||
/// <summary>
|
||||
/// Send the message for a deployment to configured deployment channels.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
@@ -431,7 +430,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
// Don't dispose it
|
||||
logger.LogDebug("Creating legacy two folder .dmb provider targeting {aDirName} directory...", LegacyADirectoryName);
|
||||
#pragma warning disable CA2000 // Dispose objects before losing scope (false positive)
|
||||
newProvider = new DmbProvider(compileJob, engineVersion, ioManager, new DisposeInvoker(CleanupAction), Path.DirectorySeparatorChar + LegacyADirectoryName);
|
||||
newProvider = new DmbProvider(compileJob, engineVersion, ioManager, new DisposeInvoker(CleanupAction), LegacyADirectoryName);
|
||||
#pragma warning restore CA2000 // Dispose objects before losing scope
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,18 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
sealed class DmbProvider : DmbProviderBase, IDmbProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Directory => ioManager.ResolvePath(CompileJob.DirectoryName!.Value.ToString() + directoryAppend);
|
||||
public override string Directory
|
||||
{
|
||||
get
|
||||
{
|
||||
var stringifiedCompileJobDirectory = CompileJob.DirectoryName!.Value.ToString();
|
||||
|
||||
if (directoryAppend != null)
|
||||
stringifiedCompileJobDirectory = ioManager.ConcatPath(stringifiedCompileJobDirectory, directoryAppend);
|
||||
|
||||
return ioManager.ResolvePath(stringifiedCompileJobDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Models.CompileJob CompileJob { get; }
|
||||
@@ -28,7 +39,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
/// <summary>
|
||||
/// Extra path to add to the end of <see cref="CompileJob.DirectoryName"/>.
|
||||
/// </summary>
|
||||
readonly string directoryAppend;
|
||||
readonly string? directoryAppend;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Action"/> to run when <see cref="DisposeAsync"/> is called.
|
||||
@@ -49,7 +60,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
EngineVersion = engineVersion ?? throw new ArgumentNullException(nameof(engineVersion));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
|
||||
this.directoryAppend = directoryAppend ?? String.Empty;
|
||||
this.directoryAppend = directoryAppend;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -187,8 +188,10 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
dest = IOManager.ResolvePath(mirrorGuid.ToString());
|
||||
|
||||
using var semaphore = taskThrottle.HasValue ? new SemaphoreSlim(taskThrottle.Value) : null;
|
||||
|
||||
var dir = await IOManager.DirectoryInfo(src, cancellationToken);
|
||||
await Task.WhenAll(MirrorDirectoryImpl(
|
||||
src,
|
||||
dir,
|
||||
dest,
|
||||
semaphore,
|
||||
securityLevel,
|
||||
@@ -230,21 +233,20 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
/// <summary>
|
||||
/// Recursively create tasks to create a hard link directory mirror of <paramref name="src"/> to <paramref name="dest"/>.
|
||||
/// </summary>
|
||||
/// <param name="src">The source directory path.</param>
|
||||
/// <param name="src">The source <see cref="IDirectoryInfo"/>.</param>
|
||||
/// <param name="dest">The destination directory path.</param>
|
||||
/// <param name="semaphore">Optional <see cref="SemaphoreSlim"/> used to limit degree of parallelism.</param>
|
||||
/// <param name="securityLevel">The launch <see cref="DreamDaemonSecurity"/> level.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="IEnumerable{T}"/> of <see cref="Task"/>s representing the running operations. The first <see cref="Task"/> returned is always the necessary call to <see cref="IIOManager.CreateDirectory(string, CancellationToken)"/>.</returns>
|
||||
/// <returns>An <see cref="IEnumerable{T}"/> of <see cref="Task"/>s representing the running operations. The first <see cref="Task"/> returned is always the necessary call to <see cref="IIOManager.CreateDirectory(string, CancellationToken)"/>.</returns>
|
||||
/// <remarks>I genuinely don't know how this will work with symlinked files. Waiting for the issue report I guess.</remarks>
|
||||
IEnumerable<Task> MirrorDirectoryImpl(string src, string dest, SemaphoreSlim? semaphore, DreamDaemonSecurity securityLevel, CancellationToken cancellationToken)
|
||||
IEnumerable<Task> MirrorDirectoryImpl(IDirectoryInfo src, string dest, SemaphoreSlim? semaphore, DreamDaemonSecurity securityLevel, CancellationToken cancellationToken)
|
||||
{
|
||||
var dir = new DirectoryInfo(src);
|
||||
Task? subdirCreationTask = null;
|
||||
var dreamDaemonWillAcceptOutOfDirectorySymlinks = securityLevel == DreamDaemonSecurity.Trusted;
|
||||
foreach (var subDirectory in dir.EnumerateDirectories())
|
||||
foreach (var subDirectory in src.EnumerateDirectories())
|
||||
{
|
||||
var mirroredName = Path.Combine(dest, subDirectory.Name);
|
||||
var mirroredName = IOManager.ConcatPath(dest, subDirectory.Name);
|
||||
|
||||
// check if we are a symbolic link
|
||||
if (subDirectory.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||
@@ -275,7 +277,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
logger.LogDebug("Recreating symlinked directory {name} as hard links...", subDirectory.Name);
|
||||
|
||||
var checkingSubdirCreationTask = true;
|
||||
foreach (var copyTask in MirrorDirectoryImpl(subDirectory.FullName, mirroredName, semaphore, securityLevel, cancellationToken))
|
||||
foreach (var copyTask in MirrorDirectoryImpl(subDirectory, mirroredName, semaphore, securityLevel, cancellationToken))
|
||||
{
|
||||
if (subdirCreationTask == null)
|
||||
{
|
||||
@@ -289,7 +291,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var fileInfo in dir.EnumerateFiles())
|
||||
foreach (var fileInfo in src.EnumerateFiles())
|
||||
{
|
||||
if (subdirCreationTask == null)
|
||||
{
|
||||
|
||||
@@ -6,8 +6,10 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
@@ -41,6 +43,11 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// <inheritdoc />
|
||||
protected override EngineType TargetEngineType => EngineType.Byond;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> <see cref="IOptionsMonitor{TOptions}"/> for the <see cref="ByondInstallerBase"/>.
|
||||
/// </summary>
|
||||
protected IOptionsMonitor<GeneralConfiguration> GeneralConfigurationOptions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to the system user's local BYOND folder.
|
||||
/// </summary>
|
||||
@@ -52,25 +59,85 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
protected abstract string DreamMakerName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL formatter string for downloading a byond version of {0:Major} {1:Minor}.
|
||||
/// Template to do ${Marker:xxx} replacements in <see cref="GeneralConfiguration.ByondZipDownloadTemplate"/>.
|
||||
/// </summary>
|
||||
protected abstract string ByondRevisionsUrlTemplate { get; }
|
||||
protected abstract string OSMarkerTemplate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IFileDownloader"/> for the <see cref="ByondInstallerBase"/>.
|
||||
/// </summary>
|
||||
readonly IFileDownloader fileDownloader;
|
||||
|
||||
/// <summary>
|
||||
/// Format a given <paramref name="byondZipDownloadTemplate"/>.
|
||||
/// </summary>
|
||||
/// <param name="semver">The BYOND version to download.</param>
|
||||
/// <param name="byondZipDownloadTemplate">The template.</param>
|
||||
/// <param name="osMarkerTemplate">The <see cref="OSMarkerTemplate"/>.</param>
|
||||
/// <returns>The formatted byond download <see cref="Uri"/>.</returns>
|
||||
/// <remarks>Exposed only for testability.</remarks>
|
||||
internal static Uri GetDownloadZipUrl(Version semver, string byondZipDownloadTemplate, string osMarkerTemplate)
|
||||
{
|
||||
// god forbid
|
||||
var guardGuid = Guid.NewGuid();
|
||||
|
||||
var url = byondZipDownloadTemplate
|
||||
.Replace("$$", guardGuid.ToString(), StringComparison.Ordinal)
|
||||
.Replace("${Major}", semver.Major.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal)
|
||||
.Replace("${Minor}", semver.Minor.ToString(CultureInfo.InvariantCulture), StringComparison.Ordinal);
|
||||
|
||||
var osMarkerPrefix = $"${{{osMarkerTemplate}:";
|
||||
var osMarkerIndex = url.IndexOf(osMarkerPrefix);
|
||||
while (osMarkerIndex != -1)
|
||||
{
|
||||
var start = osMarkerIndex + osMarkerPrefix.Length;
|
||||
var end = url.IndexOf('}', start);
|
||||
if (end == -1)
|
||||
break;
|
||||
|
||||
var substitution = url.Substring(start, end - start);
|
||||
url = url.Replace($"{osMarkerPrefix}{substitution}}}", substitution, StringComparison.Ordinal);
|
||||
|
||||
osMarkerIndex = url.IndexOf(osMarkerPrefix);
|
||||
}
|
||||
|
||||
// at this point, any other substitution attempts should be removed
|
||||
var otherMarkerPrefix = "${";
|
||||
var otherMarkerIndex = url.IndexOf(otherMarkerPrefix);
|
||||
while (otherMarkerIndex != -1)
|
||||
{
|
||||
var start = otherMarkerIndex + otherMarkerPrefix.Length;
|
||||
var end = url.IndexOf('}', start);
|
||||
if (end == -1)
|
||||
break;
|
||||
|
||||
var substitution = url.Substring(start, end - start);
|
||||
url = url.Replace($"{otherMarkerPrefix}{substitution}}}", String.Empty, StringComparison.Ordinal);
|
||||
|
||||
otherMarkerIndex = url.IndexOf(otherMarkerPrefix);
|
||||
}
|
||||
|
||||
url = url.Replace(guardGuid.ToString(), "$", StringComparison.Ordinal);
|
||||
|
||||
return new Uri(url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ByondInstallerBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="EngineInstallerBase"/>.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="EngineInstallerBase"/>.</param>
|
||||
/// <param name="fileDownloader">The value of <see cref="fileDownloader"/>.</param>
|
||||
protected ByondInstallerBase(IIOManager ioManager, ILogger<ByondInstallerBase> logger, IFileDownloader fileDownloader)
|
||||
/// <param name="generalConfigurationOptions">The value of <see cref="GeneralConfigurationOptions"/>.</param>
|
||||
protected ByondInstallerBase(
|
||||
IIOManager ioManager,
|
||||
ILogger<ByondInstallerBase> logger,
|
||||
IFileDownloader fileDownloader,
|
||||
IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions)
|
||||
: base(ioManager, logger)
|
||||
{
|
||||
this.fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader));
|
||||
GeneralConfigurationOptions = generalConfigurationOptions ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -78,7 +145,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
{
|
||||
CheckVersionValidity(version);
|
||||
|
||||
var installationIOManager = new ResolvingIOManager(IOManager, path);
|
||||
var installationIOManager = IOManager.CreateResolverForSubdirectory(path);
|
||||
var supportsMapThreads = version.Version >= MapThreadsVersion;
|
||||
|
||||
return ValueTask.FromResult<IEngineInstallation>(
|
||||
@@ -195,8 +262,12 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
Uri GetDownloadZipUrl(EngineVersion version)
|
||||
{
|
||||
CheckVersionValidity(version);
|
||||
var url = String.Format(CultureInfo.InvariantCulture, ByondRevisionsUrlTemplate, version.Version!.Major, version.Version.Minor);
|
||||
return new Uri(url);
|
||||
|
||||
var guardGuid = Guid.NewGuid();
|
||||
|
||||
var semver = version.Version!;
|
||||
var template = GeneralConfigurationOptions.CurrentValue.ByondZipDownloadTemplate;
|
||||
return GetDownloadZipUrl(semver, template, OSMarkerTemplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
var dotnetPath = (await DotnetHelper.GetDotnetPath(platformIdentifier, IOManager, cancellationToken))
|
||||
?? throw new JobException("Failed to find dotnet path!");
|
||||
return new OpenDreamInstallation(
|
||||
new ResolvingIOManager(IOManager, path),
|
||||
IOManager.CreateResolverForSubdirectory(path),
|
||||
asyncDelayer,
|
||||
httpClientFactory,
|
||||
dotnetPath,
|
||||
|
||||
@@ -5,9 +5,11 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Common.Extensions;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Engine
|
||||
@@ -39,7 +41,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
protected override string DreamMakerName => DreamMakerExecutableName + ShellScriptExtension;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string ByondRevisionsUrlTemplate => "https://www.byond.com/download/build/{0}/{0}.{1}_byond_linux.zip";
|
||||
protected override string OSMarkerTemplate => "Linux";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPostWriteHandler"/> for the <see cref="PosixByondInstaller"/>.
|
||||
@@ -52,13 +54,15 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/>.</param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
/// <param name="fileDownloader">The <see cref="IFileDownloader"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="GeneralConfiguration"/> <see cref="IOptionsMonitor{TOptions}"/>.</param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
public PosixByondInstaller(
|
||||
IPostWriteHandler postWriteHandler,
|
||||
IIOManager ioManager,
|
||||
IFileDownloader fileDownloader,
|
||||
IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions,
|
||||
ILogger<PosixByondInstaller> logger)
|
||||
: base(ioManager, logger, fileDownloader)
|
||||
: base(ioManager, logger, fileDownloader, generalConfigurationOptions)
|
||||
{
|
||||
this.postWriteHandler = postWriteHandler ?? throw new ArgumentNullException(nameof(postWriteHandler));
|
||||
|
||||
|
||||
@@ -69,18 +69,13 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
protected override string PathToUserFolder { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override string ByondRevisionsUrlTemplate => "https://www.byond.com/download/build/{0}/{0}.{1}_byond.zip";
|
||||
protected override string OSMarkerTemplate => "Windows";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IProcessExecutor"/> for the <see cref="WindowsByondInstaller"/>.
|
||||
/// </summary>
|
||||
readonly IProcessExecutor processExecutor;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> for the <see cref="WindowsByondInstaller"/>.
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SessionConfiguration"/> for the <see cref="WindowsByondInstaller"/>.
|
||||
/// </summary>
|
||||
@@ -100,7 +95,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
/// Initializes a new instance of the <see cref="WindowsByondInstaller"/> class.
|
||||
/// </summary>
|
||||
/// <param name="processExecutor">The value of <see cref="processExecutor"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptionsMonitor{TOptions}"/> containing the <see cref="GeneralConfiguration"/>.</param>
|
||||
/// <param name="sessionConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="sessionConfiguration"/>.</param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
/// <param name="fileDownloader">The <see cref="IFileDownloader"/> for the <see cref="ByondInstallerBase"/>.</param>
|
||||
@@ -109,13 +104,12 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
IProcessExecutor processExecutor,
|
||||
IIOManager ioManager,
|
||||
IFileDownloader fileDownloader,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptions<SessionConfiguration> sessionConfigurationOptions,
|
||||
ILogger<WindowsByondInstaller> logger)
|
||||
: base(ioManager, logger, fileDownloader)
|
||||
: base(ioManager, logger, fileDownloader, generalConfigurationOptions)
|
||||
{
|
||||
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
sessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions));
|
||||
|
||||
var useServiceSpecialTactics = Environment.Is64BitProcess && Environment.UserName == $"{Environment.MachineName}$";
|
||||
@@ -150,7 +144,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
installDirectXTask,
|
||||
};
|
||||
|
||||
if (!generalConfiguration.SkipAddingByondFirewallException)
|
||||
if (!GeneralConfigurationOptions.CurrentValue.SkipAddingByondFirewallException)
|
||||
{
|
||||
var firewallTask = AddDreamDaemonToFirewall(version, path, deploymentPipelineProcesses, cancellationToken);
|
||||
tasks.Add(firewallTask);
|
||||
@@ -165,7 +159,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
CheckVersionValidity(version);
|
||||
ArgumentNullException.ThrowIfNull(path);
|
||||
|
||||
if (generalConfiguration.SkipAddingByondFirewallException)
|
||||
if (GeneralConfigurationOptions.CurrentValue.SkipAddingByondFirewallException)
|
||||
return;
|
||||
|
||||
if (version.Version < DDExeVersion)
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Tgstation.Server.Host.Components.Engine
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="MemoryStream"/> containing the zip data of the engine.
|
||||
/// The <see cref="Stream"/> containing the zip data of the engine.
|
||||
/// </summary>
|
||||
readonly Stream zipStream;
|
||||
|
||||
|
||||
@@ -531,7 +531,10 @@ namespace Tgstation.Server.Host.Components
|
||||
}
|
||||
}
|
||||
else if (preserveTestMerges)
|
||||
{
|
||||
Chat.QueueRawDeploymentMessage("Automatic update has failed due to a conflicting testmerge!");
|
||||
throw new JobException(Api.Models.ErrorCode.InstanceUpdateTestMergeConflict);
|
||||
}
|
||||
|
||||
if (!preserveTestMerges)
|
||||
{
|
||||
|
||||
@@ -164,7 +164,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
/// <param name="instanceIOManager">The instance's <see cref="IIOManager"/>.</param>
|
||||
/// <returns>The <see cref="IIOManager"/> for the instance's "Game" directory.</returns>
|
||||
static ResolvingIOManager CreateGameIOManager(IIOManager instanceIOManager) => new(instanceIOManager, "Game");
|
||||
static IIOManager CreateGameIOManager(IIOManager instanceIOManager) => instanceIOManager.CreateResolverForSubdirectory("Game");
|
||||
|
||||
#pragma warning disable CA1502 // TODO: Decomplexify
|
||||
/// <summary>
|
||||
@@ -270,11 +270,11 @@ namespace Tgstation.Server.Host.Components
|
||||
var instanceIoManager = CreateInstanceIOManager(metadata);
|
||||
|
||||
// various other ioManagers
|
||||
var repoIoManager = new ResolvingIOManager(instanceIoManager, "Repository");
|
||||
var byondIOManager = new ResolvingIOManager(instanceIoManager, "Byond");
|
||||
var repoIoManager = instanceIoManager.CreateResolverForSubdirectory("Repository");
|
||||
var byondIOManager = instanceIoManager.CreateResolverForSubdirectory("Byond");
|
||||
var gameIoManager = CreateGameIOManager(instanceIoManager);
|
||||
var diagnosticsIOManager = new ResolvingIOManager(instanceIoManager, "Diagnostics");
|
||||
var configurationIoManager = new ResolvingIOManager(instanceIoManager, "Configuration");
|
||||
var diagnosticsIOManager = instanceIoManager.CreateResolverForSubdirectory("Diagnostics");
|
||||
var configurationIoManager = instanceIoManager.CreateResolverForSubdirectory("Configuration");
|
||||
|
||||
var metricFactory = this.metricFactory.WithLabels(
|
||||
new Dictionary<string, string>
|
||||
@@ -441,6 +441,6 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
/// <param name="metadata">The <see cref="Models.Instance"/>.</param>
|
||||
/// <returns>The <see cref="IIOManager"/> for the <paramref name="metadata"/>.</returns>
|
||||
ResolvingIOManager CreateInstanceIOManager(Models.Instance metadata) => new(ioManager, metadata.Path!);
|
||||
IIOManager CreateInstanceIOManager(Models.Instance metadata) => ioManager.CreateResolverForSubdirectory(metadata.Path!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,7 +702,7 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
logger.LogDebug("Running as user: {username}", Environment.UserName);
|
||||
|
||||
generalConfiguration.CheckCompatibility(logger);
|
||||
generalConfiguration.CheckCompatibility(logger, ioManager);
|
||||
|
||||
using (var systemIdentity = systemIdentityFactory.GetCurrent())
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -1099,8 +1098,8 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
=> ioManager.GetDirectoryName(libGitRepo
|
||||
.Info
|
||||
.Path
|
||||
.TrimEnd(Path.DirectorySeparatorChar)
|
||||
.TrimEnd(Path.AltDirectorySeparatorChar));
|
||||
.TrimEnd(ioManager.DirectorySeparatorChar)
|
||||
.TrimEnd(ioManager.AltDirectorySeparatorChar));
|
||||
|
||||
/// <summary>
|
||||
/// Recusively update all <see cref="Submodule"/>s in the <see cref="libGitRepo"/>.
|
||||
|
||||
@@ -333,10 +333,10 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
},
|
||||
async cancellationToken =>
|
||||
{
|
||||
FileStream? result = null;
|
||||
Stream? result = null;
|
||||
void GetFileStream()
|
||||
{
|
||||
result = ioManager.GetFileStream(path, false);
|
||||
result = synchronousIOManager.GetFileStream(path);
|
||||
}
|
||||
|
||||
if (systemIdentity == null)
|
||||
@@ -771,7 +771,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
var nullOrEmptyCheck = String.IsNullOrEmpty(configurationRelativePath);
|
||||
if (nullOrEmptyCheck)
|
||||
configurationRelativePath = DefaultIOManager.CurrentDirectory;
|
||||
if (configurationRelativePath![0] == Path.DirectorySeparatorChar || configurationRelativePath[0] == Path.AltDirectorySeparatorChar)
|
||||
if (configurationRelativePath![0] == ioManager.DirectorySeparatorChar || configurationRelativePath[0] == ioManager.AltDirectorySeparatorChar)
|
||||
configurationRelativePath = DefaultIOManager.CurrentDirectory + configurationRelativePath;
|
||||
var resolved = ioManager.ResolvePath(configurationRelativePath);
|
||||
var local = !nullOrEmptyCheck ? ioManager.ResolvePath() : null;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -9,6 +8,7 @@ using Newtonsoft.Json.Converters;
|
||||
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Properties;
|
||||
using Tgstation.Server.Host.Setup;
|
||||
|
||||
@@ -31,6 +31,11 @@ namespace Tgstation.Server.Host.Configuration
|
||||
/// </summary>
|
||||
public const ushort DefaultApiPort = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// Default vale for <see cref="ByondZipDownloadTemplate"/>.
|
||||
/// </summary>
|
||||
public const string DefaultByondZipDownloadTemplate = "https://www.byond.com/download/build/${Major}/${Major}.${Minor}_byond${Linux:_linux}.zip";
|
||||
|
||||
/// <summary>
|
||||
/// The default value for <see cref="ServerInformationBase.MinimumPasswordLength"/>.
|
||||
/// </summary>
|
||||
@@ -148,6 +153,17 @@ namespace Tgstation.Server.Host.Configuration
|
||||
[YamlMember(SerializeAs = typeof(string))]
|
||||
public Uri OpenDreamGitUrl { get; set; } = new Uri(DefaultOpenDreamGitUrl);
|
||||
|
||||
/// <summary>
|
||||
/// The formatter used to download official byond zip files for a given version
|
||||
/// - ${Major} is substituted with the major version number
|
||||
/// - ${Minor} is substituted with the minor version number
|
||||
/// - ${Linux:xxx}, where xxx is any string, will be substituted with xxx if running under Linux.
|
||||
/// - ${Windows:xxx}, where xxx is any string, will be substituted with xxx if running under Windows.
|
||||
/// - $$ will evaluate to a literal $ and not be used for substitutions.
|
||||
/// - Any inapplicable ${xxx} string will be removed.
|
||||
/// </summary>
|
||||
public string ByondZipDownloadTemplate { get; set; } = DefaultByondZipDownloadTemplate;
|
||||
|
||||
/// <summary>
|
||||
/// The prefix to the OpenDream semver as tags appear in the git repository.
|
||||
/// </summary>
|
||||
@@ -178,7 +194,8 @@ namespace Tgstation.Server.Host.Configuration
|
||||
/// Validates the current <see cref="ConfigVersion"/>'s compatibility and provides migration instructions.
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="ILogger"/> to use.</param>
|
||||
public void CheckCompatibility(ILogger logger)
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> to use.</param>
|
||||
public void CheckCompatibility(ILogger logger, IIOManager ioManager)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
@@ -204,7 +221,7 @@ namespace Tgstation.Server.Host.Configuration
|
||||
if (ByondTopicTimeout <= 1000)
|
||||
logger.LogWarning("The timeout for sending BYOND topics is very low ({ms}ms). Topic calls may fail to complete at all!", ByondTopicTimeout);
|
||||
|
||||
if (AdditionalEventScriptsDirectories?.Any(path => !Path.IsPathRooted(path)) == true)
|
||||
if (AdditionalEventScriptsDirectories?.Any(path => !ioManager.IsPathRooted(path)) == true)
|
||||
logger.LogWarning($"Config option \"{nameof(AdditionalEventScriptsDirectories)}\" contains non-rooted paths. These will be evaluated relative to each instances \"Configuration\" directory!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
|
||||
@@ -106,10 +107,12 @@ namespace Tgstation.Server.Host.Core
|
||||
public static IServerFactory CreateDefaultServerFactory()
|
||||
{
|
||||
var assemblyInformationProvider = new AssemblyInformationProvider();
|
||||
var ioManager = new DefaultIOManager();
|
||||
var fileSystem = new FileSystem();
|
||||
var ioManager = new DefaultIOManager(fileSystem);
|
||||
return new ServerFactory(
|
||||
assemblyInformationProvider,
|
||||
ioManager);
|
||||
ioManager,
|
||||
fileSystem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -155,11 +158,13 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> needed for configuration.</param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> needed for configuration.</param>
|
||||
/// <param name="postSetupServices">The <see cref="IPostSetupServices"/> needed for configuration.</param>
|
||||
/// <param name="fileSystem">The <see cref="IFileSystem"/> needed for configuration.</param>
|
||||
public void ConfigureServices(
|
||||
IServiceCollection services,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
IIOManager ioManager,
|
||||
IPostSetupServices postSetupServices)
|
||||
IPostSetupServices postSetupServices,
|
||||
IFileSystem fileSystem)
|
||||
{
|
||||
ConfigureServices(services, assemblyInformationProvider, ioManager);
|
||||
|
||||
@@ -470,8 +475,7 @@ namespace Tgstation.Server.Host.Core
|
||||
services => services
|
||||
.GetRequiredService<IRepositoryManagerFactory>()
|
||||
.CreateRepositoryManager(
|
||||
new ResolvingIOManager(
|
||||
services.GetRequiredService<IIOManager>(),
|
||||
services.GetRequiredService<IIOManager>().CreateResolverForSubdirectory(
|
||||
openDreamRepositoryDirectory),
|
||||
new NoopEventConsumer()));
|
||||
|
||||
@@ -527,6 +531,7 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton<ISynchronousIOManager, SynchronousIOManager>();
|
||||
services.AddSingleton<IServerPortProvider, ServerPortProivder>();
|
||||
services.AddSingleton<ITopicClientFactory, TopicClientFactory>();
|
||||
services.AddSingleton(fileSystem);
|
||||
services.AddHostedService<CommandPipeManager>();
|
||||
services.AddHostedService<VersionReportingService>();
|
||||
|
||||
@@ -940,10 +945,7 @@ namespace Tgstation.Server.Host.Core
|
||||
options.Scope.Add(OpenIdConnectScope.OpenId);
|
||||
options.Scope.Add(OpenIdConnectScope.OfflineAccess);
|
||||
|
||||
#if DEBUG
|
||||
options.RequireHttpsMetadata = false;
|
||||
#endif
|
||||
|
||||
options.SaveTokens = true;
|
||||
options.ResponseType = OpenIdConnectResponseType.Code;
|
||||
options.MapInboundClaims = false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO.Abstractions;
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
@@ -26,24 +27,27 @@ namespace Tgstation.Server.Host.Extensions
|
||||
/// <param name="builder">The <see cref="IWebHostBuilder"/> to configure.</param>
|
||||
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> to use.</param>
|
||||
/// <param name="ioManager">The <see cref="IIOManager"/> to use.</param>
|
||||
/// <returns>The configured <paramref name="builder"/>.</returns>
|
||||
/// <param name="postSetupServices">The <see cref="IPostSetupServices"/> to use.</param>
|
||||
/// <param name="fileSystem">The <see cref="IFileSystem"/> to use.</param>
|
||||
/// <returns>The configured <paramref name="builder"/>.</returns>
|
||||
public static IWebHostBuilder UseApplication(
|
||||
this IWebHostBuilder builder,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
IIOManager ioManager,
|
||||
IPostSetupServices postSetupServices)
|
||||
IPostSetupServices postSetupServices,
|
||||
IFileSystem fileSystem)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
|
||||
ArgumentNullException.ThrowIfNull(ioManager);
|
||||
ArgumentNullException.ThrowIfNull(postSetupServices);
|
||||
ArgumentNullException.ThrowIfNull(fileSystem);
|
||||
|
||||
return builder.ConfigureServices(
|
||||
(context, services) =>
|
||||
{
|
||||
var application = new Application(context.Configuration, context.HostingEnvironment);
|
||||
application.ConfigureServices(services, assemblyInformationProvider, ioManager, postSetupServices);
|
||||
application.ConfigureServices(services, assemblyInformationProvider, ioManager, postSetupServices, fileSystem);
|
||||
services.AddSingleton(application);
|
||||
})
|
||||
.Configure(ConfigureApplication);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -30,12 +31,32 @@ namespace Tgstation.Server.Host.IO
|
||||
/// </summary>
|
||||
public const TaskCreationOptions BlockingTaskCreationOptions = TaskCreationOptions.None;
|
||||
|
||||
/// <inheritdoc />
|
||||
public char DirectorySeparatorChar => fileSystem.Path.DirectorySeparatorChar;
|
||||
|
||||
/// <inheritdoc />
|
||||
public char AltDirectorySeparatorChar => fileSystem.Path.AltDirectorySeparatorChar;
|
||||
|
||||
/// <summary>
|
||||
/// The backing <see cref="IFileSystem"/>.
|
||||
/// </summary>
|
||||
readonly IFileSystem fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultIOManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The value of <see cref="fileSystem"/>.</param>
|
||||
public DefaultIOManager(IFileSystem fileSystem)
|
||||
{
|
||||
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively empty a directory.
|
||||
/// </summary>
|
||||
/// <param name="dir"><see cref="DirectoryInfo"/> of the directory to empty.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
static void NormalizeAndDelete(DirectoryInfo dir, CancellationToken cancellationToken)
|
||||
static void NormalizeAndDelete(IDirectoryInfo dir, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -111,7 +132,7 @@ namespace Tgstation.Server.Host.IO
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ConcatPath(params string[] paths) => Path.Combine(paths);
|
||||
public string ConcatPath(params string[] paths) => fileSystem.Path.Combine(paths);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask CopyFile(string src, string dest, CancellationToken cancellationToken)
|
||||
@@ -120,7 +141,7 @@ namespace Tgstation.Server.Host.IO
|
||||
ArgumentNullException.ThrowIfNull(dest);
|
||||
|
||||
// tested to hell and back, these are the optimal buffer sizes
|
||||
await using var srcStream = new FileStream(
|
||||
await using var srcStream = fileSystem.FileStream.New(
|
||||
ResolvePath(src),
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
@@ -134,41 +155,40 @@ namespace Tgstation.Server.Host.IO
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateDirectory(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.CreateDirectory(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
public Task CreateDirectory(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => fileSystem.Directory.CreateDirectory(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task DeleteDirectory(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
path = ResolvePath(path);
|
||||
var di = new DirectoryInfo(path);
|
||||
if (!di.Exists)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return Task.Factory.StartNew(
|
||||
() => NormalizeAndDelete(di, cancellationToken),
|
||||
=> Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
var di = fileSystem.DirectoryInfo.New(
|
||||
ResolvePath(path));
|
||||
if (di.Exists)
|
||||
NormalizeAndDelete(di, cancellationToken);
|
||||
},
|
||||
cancellationToken,
|
||||
BlockingTaskCreationOptions,
|
||||
TaskScheduler.Current);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task DeleteFile(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Delete(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
public Task DeleteFile(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => fileSystem.File.Delete(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> FileExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => File.Exists(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
public Task<bool> FileExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => fileSystem.File.Exists(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> DirectoryExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => Directory.Exists(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
public Task<bool> DirectoryExists(string path, CancellationToken cancellationToken) => Task.Factory.StartNew(() => fileSystem.Directory.Exists(ResolvePath(path)), cancellationToken, BlockingTaskCreationOptions, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetDirectoryName(string path) => Path.GetDirectoryName(path ?? throw new ArgumentNullException(nameof(path)))
|
||||
public string GetDirectoryName(string path) => fileSystem.Path.GetDirectoryName(path ?? throw new ArgumentNullException(nameof(path)))
|
||||
?? throw new InvalidOperationException($"Null was returned. Path ({path}) must be rooted. This is not supported!");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetFileName(string path) => Path.GetFileName(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
public string GetFileName(string path) => fileSystem.Path.GetFileName(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetFileNameWithoutExtension(string path) => Path.GetFileNameWithoutExtension(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
public string GetFileNameWithoutExtension(string path) => fileSystem.Path.GetFileNameWithoutExtension(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<List<string>> GetFilesWithExtension(string path, string extension, bool recursive, CancellationToken cancellationToken) => Task.Factory.StartNew(
|
||||
@@ -177,7 +197,7 @@ namespace Tgstation.Server.Host.IO
|
||||
path = ResolvePath(path);
|
||||
ArgumentNullException.ThrowIfNull(extension);
|
||||
var results = new List<string>();
|
||||
foreach (var fileName in Directory.EnumerateFiles(
|
||||
foreach (var fileName in fileSystem.Directory.EnumerateFiles(
|
||||
path,
|
||||
$"*.{extension}",
|
||||
recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
|
||||
@@ -199,7 +219,7 @@ namespace Tgstation.Server.Host.IO
|
||||
ArgumentNullException.ThrowIfNull(destination);
|
||||
source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source)));
|
||||
destination = ResolvePath(destination);
|
||||
File.Move(source, destination);
|
||||
fileSystem.File.Move(source, destination);
|
||||
},
|
||||
cancellationToken,
|
||||
BlockingTaskCreationOptions,
|
||||
@@ -212,7 +232,7 @@ namespace Tgstation.Server.Host.IO
|
||||
ArgumentNullException.ThrowIfNull(destination);
|
||||
source = ResolvePath(source ?? throw new ArgumentNullException(nameof(source)));
|
||||
destination = ResolvePath(destination);
|
||||
Directory.Move(source, destination);
|
||||
fileSystem.Directory.Move(source, destination);
|
||||
},
|
||||
cancellationToken,
|
||||
BlockingTaskCreationOptions,
|
||||
@@ -221,7 +241,7 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <inheritdoc />
|
||||
public async ValueTask<byte[]> ReadAllBytes(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
await using var file = CreateAsyncSequentialReadStream(path);
|
||||
await using var file = CreateAsyncReadStream(path, true, true);
|
||||
byte[] buf;
|
||||
buf = new byte[file.Length];
|
||||
await file.ReadAsync(buf, cancellationToken);
|
||||
@@ -229,10 +249,12 @@ namespace Tgstation.Server.Host.IO
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ResolvePath() => ResolvePath(CurrentDirectory);
|
||||
public string ResolvePath()
|
||||
=> ResolvePath(CurrentDirectory);
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual string ResolvePath(string path) => Path.GetFullPath(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
public virtual string ResolvePath(string path)
|
||||
=> fileSystem.Path.GetFullPath(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
|
||||
@@ -242,10 +264,10 @@ namespace Tgstation.Server.Host.IO
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public FileStream CreateAsyncSequentialWriteStream(string path)
|
||||
public Stream CreateAsyncSequentialWriteStream(string path)
|
||||
{
|
||||
path = ResolvePath(path);
|
||||
return new FileStream(
|
||||
return fileSystem.FileStream.New(
|
||||
path,
|
||||
FileMode.Create,
|
||||
FileAccess.Write,
|
||||
@@ -255,16 +277,18 @@ namespace Tgstation.Server.Host.IO
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public FileStream CreateAsyncSequentialReadStream(string path)
|
||||
public Stream CreateAsyncReadStream(string path, bool sequental, bool shareWrite)
|
||||
{
|
||||
path = ResolvePath(path);
|
||||
return new FileStream(
|
||||
return fileSystem.FileStream.New(
|
||||
path,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.ReadWrite | FileShare.Delete,
|
||||
FileShare.ReadWrite | FileShare.Delete | (shareWrite ? FileShare.Write : FileShare.None),
|
||||
DefaultBufferSize,
|
||||
FileOptions.Asynchronous | FileOptions.SequentialScan);
|
||||
sequental
|
||||
? FileOptions.Asynchronous | FileOptions.SequentialScan
|
||||
: FileOptions.Asynchronous);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -274,7 +298,7 @@ namespace Tgstation.Server.Host.IO
|
||||
path = ResolvePath(path);
|
||||
var results = new List<string>();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
foreach (var directoryName in Directory.EnumerateDirectories(path))
|
||||
foreach (var directoryName in fileSystem.Directory.EnumerateDirectories(path))
|
||||
{
|
||||
results.Add(directoryName);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -293,7 +317,7 @@ namespace Tgstation.Server.Host.IO
|
||||
path = ResolvePath(path);
|
||||
var results = new List<string>();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
foreach (var fileName in Directory.EnumerateFiles(path))
|
||||
foreach (var fileName in fileSystem.Directory.EnumerateFiles(path))
|
||||
{
|
||||
results.Add(fileName);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -331,8 +355,8 @@ namespace Tgstation.Server.Host.IO
|
||||
public bool PathContainsParentAccess(string path) => path
|
||||
?.Split(
|
||||
[
|
||||
Path.DirectorySeparatorChar,
|
||||
Path.AltDirectorySeparatorChar,
|
||||
fileSystem.Path.DirectorySeparatorChar,
|
||||
fileSystem.Path.AltDirectorySeparatorChar,
|
||||
])
|
||||
.Any(x => x == "..")
|
||||
?? throw new ArgumentNullException(nameof(path));
|
||||
@@ -342,22 +366,13 @@ namespace Tgstation.Server.Host.IO
|
||||
() =>
|
||||
{
|
||||
path = ResolvePath(path ?? throw new ArgumentNullException(nameof(path)));
|
||||
var fileInfo = new FileInfo(path);
|
||||
var fileInfo = fileSystem.FileInfo.New(path);
|
||||
return new DateTimeOffset(fileInfo.LastWriteTimeUtc);
|
||||
},
|
||||
cancellationToken,
|
||||
BlockingTaskCreationOptions,
|
||||
TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public FileStream GetFileStream(string path, bool shareWrite) => new(
|
||||
ResolvePath(path),
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.Read | FileShare.Delete | (shareWrite ? FileShare.Write : FileShare.None),
|
||||
DefaultBufferSize,
|
||||
true);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> PathIsChildOf(string parentPath, string childPath, CancellationToken cancellationToken) => Task.Factory.StartNew(
|
||||
() =>
|
||||
@@ -369,8 +384,8 @@ namespace Tgstation.Server.Host.IO
|
||||
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);
|
||||
var di1 = fileSystem.DirectoryInfo.New(parentPath);
|
||||
var di2 = fileSystem.DirectoryInfo.New(childPath);
|
||||
while (di2.Parent != null)
|
||||
{
|
||||
if (di2.Parent.FullName == di1.FullName)
|
||||
@@ -385,6 +400,33 @@ namespace Tgstation.Server.Host.IO
|
||||
BlockingTaskCreationOptions,
|
||||
TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IDirectoryInfo> DirectoryInfo(string path, CancellationToken cancellationToken)
|
||||
=> Task.Factory.StartNew(
|
||||
() => fileSystem.DirectoryInfo.New(ResolvePath(path)),
|
||||
cancellationToken,
|
||||
BlockingTaskCreationOptions,
|
||||
TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsPathRooted(string path)
|
||||
=> fileSystem.Path.IsPathRooted(path);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IIOManager CreateResolverForSubdirectory(string subdirectoryPath)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(subdirectoryPath);
|
||||
|
||||
if (!Path.IsPathRooted(subdirectoryPath))
|
||||
subdirectoryPath = ConcatPath(
|
||||
ResolvePath(),
|
||||
subdirectoryPath);
|
||||
|
||||
return new ResolvingIOManager(
|
||||
fileSystem,
|
||||
subdirectoryPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies a directory from <paramref name="src"/> to <paramref name="dest"/>.
|
||||
/// </summary>
|
||||
@@ -403,7 +445,7 @@ namespace Tgstation.Server.Host.IO
|
||||
SemaphoreSlim? semaphore,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var dir = new DirectoryInfo(src);
|
||||
var dir = fileSystem.DirectoryInfo.New(src);
|
||||
Task? subdirCreationTask = null;
|
||||
foreach (var subDirectory in dir.EnumerateDirectories())
|
||||
{
|
||||
@@ -411,7 +453,7 @@ namespace Tgstation.Server.Host.IO
|
||||
continue;
|
||||
|
||||
var checkingSubdirCreationTask = true;
|
||||
foreach (var copyTask in CopyDirectoryImpl(subDirectory.FullName, Path.Combine(dest, subDirectory.Name), null, postCopyCallback, semaphore, cancellationToken))
|
||||
foreach (var copyTask in CopyDirectoryImpl(subDirectory.FullName, fileSystem.Path.Combine(dest, subDirectory.Name), null, postCopyCallback, semaphore, cancellationToken))
|
||||
{
|
||||
if (subdirCreationTask == null)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -11,6 +12,23 @@ namespace Tgstation.Server.Host.IO
|
||||
/// </summary>
|
||||
public interface IIOManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the primary directory separator character.
|
||||
/// </summary>
|
||||
char DirectorySeparatorChar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the alternative directory separator character.
|
||||
/// </summary>
|
||||
char AltDirectorySeparatorChar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="IIOManager"/> that resolves paths to the specified <paramref name="subdirectoryPath"/>.
|
||||
/// </summary>
|
||||
/// <param name="subdirectoryPath">A relative or absolute path that the new <see cref="IIOManager"/> will resolve as its current directory.</param>
|
||||
/// <returns>A new <see cref="IIOManager"/>.</returns>
|
||||
IIOManager CreateResolverForSubdirectory(string subdirectoryPath);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the full path of the current working directory.
|
||||
/// </summary>
|
||||
@@ -117,15 +135,17 @@ namespace Tgstation.Server.Host.IO
|
||||
/// Creates an asynchronous <see cref="FileStream"/> for sequential writing.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file to write, will be truncated.</param>
|
||||
/// <returns>The open <see cref="FileStream"/>.</returns>
|
||||
FileStream CreateAsyncSequentialWriteStream(string path);
|
||||
/// <returns>The open <see cref="Stream"/>.</returns>
|
||||
Stream CreateAsyncSequentialWriteStream(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an asynchronous <see cref="FileStream"/> for sequential reading.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file to write, will be truncated.</param>
|
||||
/// <returns>The open <see cref="FileStream"/>.</returns>
|
||||
FileStream CreateAsyncSequentialReadStream(string path);
|
||||
/// <param name="sequential">If the sequential read flag should be added.</param>
|
||||
/// <param name="shareWrite">If <see cref="FileShare.Write"/> should be used.</param>
|
||||
/// <returns>The open <see cref="Stream"/>.</returns>
|
||||
Stream CreateAsyncReadStream(string path, bool sequential, bool shareWrite);
|
||||
|
||||
/// <summary>
|
||||
/// Writes some <paramref name="contents"/> to a file at <paramref name="path"/> overwriting previous content.
|
||||
@@ -230,12 +250,18 @@ namespace Tgstation.Server.Host.IO
|
||||
Task<DateTimeOffset> GetLastModified(string path, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Stream"/> for a given file <paramref name="path"/>.
|
||||
/// Gets a <see cref="IDirectoryInfo"/> for the given <paramref name="path"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file.</param>
|
||||
/// <param name="shareWrite">If <see cref="FileShare.Write"/> should be used.</param>
|
||||
/// <returns>The <see cref="FileStream"/> of the file.</returns>
|
||||
/// <remarks>This function is sychronous.</remarks>
|
||||
FileStream GetFileStream(string path, bool shareWrite);
|
||||
/// <param name="path">The path to get <see cref="IDirectoryInfo"/> for.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IDirectoryInfo"/> of the <paramref name="path"/>.</returns>
|
||||
Task<IDirectoryInfo> DirectoryInfo(string path, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a given <paramref name="path"/> is at the root level of the filesystem.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to check.</param>
|
||||
/// <returns><see langword="true"/> if the path is rooted, <see langword="false"/> otherwise.</returns>
|
||||
bool IsPathRooted(string path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,5 +63,12 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <param name="path">The path to check.</param>
|
||||
/// <returns><see langword="true"/> if <paramref name="path"/> is a directory, <see langword="false"/> otherwise.</returns>
|
||||
bool IsDirectory(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Stream"/> for a given file <paramref name="path"/> without write share.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file.</param>
|
||||
/// <returns>The <see cref="Stream"/> of the file.</returns>
|
||||
Stream GetFileStream(string path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -15,6 +15,20 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <inheritdoc />
|
||||
public bool SymlinkedDirectoriesAreDeletedAsFiles => true;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IFileSystem"/> to use.
|
||||
/// </summary>
|
||||
readonly IFileSystem fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PosixFilesystemLinkFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The value of <see cref="fileSystem"/>.</param>
|
||||
public PosixFilesystemLinkFactory(IFileSystem fileSystem)
|
||||
{
|
||||
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateHardLink(string targetPath, string linkPath, CancellationToken cancellationToken) => Task.Factory.StartNew(
|
||||
() =>
|
||||
@@ -39,7 +53,7 @@ namespace Tgstation.Server.Host.IO
|
||||
ArgumentNullException.ThrowIfNull(linkPath);
|
||||
|
||||
UnixFileSystemInfo fsInfo;
|
||||
var isFile = File.Exists(targetPath);
|
||||
var isFile = fileSystem.File.Exists(targetPath);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (isFile)
|
||||
fsInfo = new UnixFileInfo(targetPath);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
|
||||
namespace Tgstation.Server.Host.IO
|
||||
{
|
||||
@@ -16,20 +16,20 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ResolvingIOManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="parent">The <see cref="IIOManager"/> that resolves to the directory to work out of.</param>
|
||||
/// <param name="fileSystem">The <see cref="IFileSystem"/> for the <see cref="DefaultIOManager"/>.</param>
|
||||
/// <param name="subdirectory">The value of <see cref="subdirectory"/>.</param>
|
||||
public ResolvingIOManager(IIOManager parent, string subdirectory)
|
||||
public ResolvingIOManager(
|
||||
IFileSystem fileSystem,
|
||||
string subdirectory)
|
||||
: base(fileSystem)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(parent);
|
||||
ArgumentNullException.ThrowIfNull(subdirectory);
|
||||
|
||||
this.subdirectory = ConcatPath(parent.ResolvePath(), subdirectory);
|
||||
this.subdirectory = subdirectory ?? throw new ArgumentNullException(nameof(subdirectory));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ResolvePath(string path)
|
||||
{
|
||||
if (!Path.IsPathRooted(path))
|
||||
if (!IsPathRooted(path))
|
||||
return base.ResolvePath(ConcatPath(subdirectory, path));
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
@@ -13,6 +14,11 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <inheritdoc />
|
||||
sealed class SynchronousIOManager : ISynchronousIOManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IFileSystem"/> to use.
|
||||
/// </summary>
|
||||
readonly IFileSystem fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="SynchronousIOManager"/>.
|
||||
/// </summary>
|
||||
@@ -21,9 +27,11 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SynchronousIOManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The value of <see cref="fileSystem"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public SynchronousIOManager(ILogger<SynchronousIOManager> logger)
|
||||
public SynchronousIOManager(IFileSystem fileSystem, ILogger<SynchronousIOManager> logger)
|
||||
{
|
||||
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
@@ -33,32 +41,32 @@ namespace Tgstation.Server.Host.IO
|
||||
if (IsDirectory(path))
|
||||
return true;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Directory.CreateDirectory(path);
|
||||
fileSystem.Directory.CreateDirectory(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool DeleteDirectory(string path)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
if (fileSystem.File.Exists(path))
|
||||
return false;
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
if (!fileSystem.Directory.Exists(path))
|
||||
return true;
|
||||
|
||||
if (Directory.EnumerateFileSystemEntries(path).Any())
|
||||
if (fileSystem.Directory.EnumerateFileSystemEntries(path).Any())
|
||||
return false;
|
||||
|
||||
Directory.Delete(path);
|
||||
fileSystem.Directory.Delete(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> GetDirectories(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var directoryName in Directory.EnumerateDirectories(path))
|
||||
foreach (var directoryName in fileSystem.Directory.EnumerateDirectories(path))
|
||||
{
|
||||
yield return Path.GetFileName(directoryName);
|
||||
yield return fileSystem.Path.GetFileName(directoryName);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
@@ -66,9 +74,9 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> GetFiles(string path, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var fileName in Directory.EnumerateFiles(path))
|
||||
foreach (var fileName in fileSystem.Directory.EnumerateFiles(path))
|
||||
{
|
||||
yield return Path.GetFileName(fileName);
|
||||
yield return fileSystem.Path.GetFileName(fileName);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
@@ -77,14 +85,14 @@ namespace Tgstation.Server.Host.IO
|
||||
public bool IsDirectory(string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(path);
|
||||
return Directory.Exists(path);
|
||||
return fileSystem.Directory.Exists(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte[] ReadFile(string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(path);
|
||||
return File.ReadAllBytes(path);
|
||||
return fileSystem.File.ReadAllBytes(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -94,16 +102,16 @@ namespace Tgstation.Server.Host.IO
|
||||
ArgumentNullException.ThrowIfNull(data);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException("path cannot be rooted!", nameof(path));
|
||||
Directory.CreateDirectory(directory);
|
||||
var directory = fileSystem.Path.GetDirectoryName(path) ?? throw new ArgumentException("path cannot be rooted!", nameof(path));
|
||||
fileSystem.Directory.CreateDirectory(directory);
|
||||
|
||||
var newFile = !File.Exists(path);
|
||||
var newFile = !fileSystem.File.Exists(path);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
logger.LogTrace("Starting checked write to {path} ({fileType} file)", path, newFile ? "New" : "Pre-existing");
|
||||
|
||||
using (var file = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
|
||||
using (var file = fileSystem.File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -160,10 +168,20 @@ namespace Tgstation.Server.Host.IO
|
||||
if (data.Length == 0)
|
||||
{
|
||||
logger.LogDebug("Stream is empty, deleting file");
|
||||
File.Delete(path);
|
||||
fileSystem.File.Delete(path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream GetFileStream(string path)
|
||||
=> fileSystem.FileStream.New(
|
||||
path,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.Read | FileShare.Delete,
|
||||
DefaultIOManager.DefaultBufferSize,
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -16,6 +16,20 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <inheritdoc />
|
||||
public bool SymlinkedDirectoriesAreDeletedAsFiles => false;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IFileSystem"/> to use.
|
||||
/// </summary>
|
||||
readonly IFileSystem fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WindowsFilesystemLinkFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="fileSystem">The value of <see cref="fileSystem"/>.</param>
|
||||
public WindowsFilesystemLinkFactory(IFileSystem fileSystem)
|
||||
{
|
||||
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateHardLink(string targetPath, string linkPath, CancellationToken cancellationToken)
|
||||
=> throw new NotSupportedException();
|
||||
@@ -28,7 +42,7 @@ namespace Tgstation.Server.Host.IO
|
||||
ArgumentNullException.ThrowIfNull(linkPath);
|
||||
|
||||
// check if its not a file
|
||||
var flags = File.Exists(targetPath) ? NativeMethods.CreateSymbolicLinkFlags.None : NativeMethods.CreateSymbolicLinkFlags.Directory;
|
||||
var flags = fileSystem.File.Exists(targetPath) ? NativeMethods.CreateSymbolicLinkFlags.None : NativeMethods.CreateSymbolicLinkFlags.Directory;
|
||||
|
||||
/*
|
||||
* no don't fucking use this
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -29,23 +30,33 @@ namespace Tgstation.Server.Host
|
||||
/// </summary>
|
||||
public const string AppSettings = "appsettings";
|
||||
|
||||
/// <inheritdoc />
|
||||
public IIOManager IOManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="ServerFactory"/>.
|
||||
/// </summary>
|
||||
readonly IAssemblyInformationProvider assemblyInformationProvider;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IIOManager IOManager { get; }
|
||||
/// <summary>
|
||||
/// The <see cref="IFileSystem"/> for the <see cref="ServerFactory"/>.
|
||||
/// </summary>
|
||||
readonly IFileSystem fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
|
||||
/// <param name="ioManager">The value of <see cref="IOManager"/>.</param>
|
||||
internal ServerFactory(IAssemblyInformationProvider assemblyInformationProvider, IIOManager ioManager)
|
||||
/// <param name="fileSystem">The value of <see cref="fileSystem"/>.</param>
|
||||
internal ServerFactory(
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
IIOManager ioManager,
|
||||
IFileSystem fileSystem)
|
||||
{
|
||||
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
|
||||
IOManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -160,7 +171,7 @@ namespace Tgstation.Server.Host
|
||||
})
|
||||
.UseIIS()
|
||||
.UseIISIntegration()
|
||||
.UseApplication(assemblyInformationProvider, IOManager, postSetupServices)
|
||||
.UseApplication(assemblyInformationProvider, IOManager, postSetupServices, fileSystem)
|
||||
.SuppressStatusMessages(true)
|
||||
.UseShutdownTimeout(
|
||||
TimeSpan.FromMinutes(
|
||||
|
||||
@@ -304,7 +304,7 @@ namespace Tgstation.Server.Host.Setup
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in the SQLite database path to store in the configuration.</returns>
|
||||
async ValueTask<string?> ValidateNonExistantSqliteDBName(string databaseName, CancellationToken cancellationToken)
|
||||
{
|
||||
var dbPathIsRooted = Path.IsPathRooted(databaseName);
|
||||
var dbPathIsRooted = ioManager.IsPathRooted(databaseName);
|
||||
var resolvedPath = ioManager.ResolvePath(
|
||||
dbPathIsRooted
|
||||
? databaseName
|
||||
|
||||
@@ -64,39 +64,6 @@ namespace Tgstation.Server.Host.System
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets potential paths to the gcore executable.
|
||||
/// </summary>
|
||||
/// <returns>The potential paths to the gcore executable.</returns>
|
||||
static IEnumerable<string> GetPotentialGCorePaths()
|
||||
{
|
||||
var enviromentPath = Environment.GetEnvironmentVariable("PATH");
|
||||
IEnumerable<string> enumerator;
|
||||
if (enviromentPath == null)
|
||||
enumerator = Enumerable.Empty<string>();
|
||||
else
|
||||
{
|
||||
var paths = enviromentPath.Split(';');
|
||||
enumerator = paths
|
||||
.Select(x => x.Split(':'))
|
||||
.SelectMany(x => x);
|
||||
}
|
||||
|
||||
var exeName = "gcore";
|
||||
|
||||
enumerator = enumerator
|
||||
.Concat(new List<string>(2)
|
||||
{
|
||||
"/usr/bin",
|
||||
"/usr/share/bin",
|
||||
"/bin",
|
||||
});
|
||||
|
||||
enumerator = enumerator.Select(x => Path.Combine(x, exeName));
|
||||
|
||||
return enumerator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResumeProcess(global::System.Diagnostics.Process process)
|
||||
{
|
||||
@@ -201,8 +168,8 @@ namespace Tgstation.Server.Host.System
|
||||
{
|
||||
// can't use ReadAllBytes here, /proc files have 0 length so the buffer is initialized to empty
|
||||
// https://stackoverflow.com/questions/12237712/how-can-i-show-the-size-of-files-in-proc-it-should-not-be-size-zero
|
||||
await using var fileStream = ioManager.CreateAsyncSequentialReadStream(
|
||||
"/proc/self/oom_score_adj");
|
||||
await using var fileStream = ioManager.CreateAsyncReadStream(
|
||||
"/proc/self/oom_score_adj", true, true);
|
||||
using var reader = new StreamReader(fileStream, Encoding.UTF8, leaveOpen: true);
|
||||
originalString = await reader.ReadToEndAsync(cancellationToken);
|
||||
}
|
||||
@@ -255,5 +222,38 @@ namespace Tgstation.Server.Host.System
|
||||
Encoding.UTF8.GetBytes(adjustedValue.ToString(CultureInfo.InvariantCulture)),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets potential paths to the gcore executable.
|
||||
/// </summary>
|
||||
/// <returns>The potential paths to the gcore executable.</returns>
|
||||
IEnumerable<string> GetPotentialGCorePaths()
|
||||
{
|
||||
var enviromentPath = Environment.GetEnvironmentVariable("PATH");
|
||||
IEnumerable<string> enumerator;
|
||||
if (enviromentPath == null)
|
||||
enumerator = Enumerable.Empty<string>();
|
||||
else
|
||||
{
|
||||
var paths = enviromentPath.Split(';');
|
||||
enumerator = paths
|
||||
.Select(x => x.Split(':'))
|
||||
.SelectMany(x => x);
|
||||
}
|
||||
|
||||
var exeName = "gcore";
|
||||
|
||||
enumerator = enumerator
|
||||
.Concat(new List<string>(2)
|
||||
{
|
||||
"/usr/bin",
|
||||
"/usr/share/bin",
|
||||
"/bin",
|
||||
});
|
||||
|
||||
enumerator = enumerator.Select(x => ioManager.ConcatPath(x, exeName));
|
||||
|
||||
return enumerator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ namespace Tgstation.Server.Host.System
|
||||
throw new JobException(ErrorCode.GameServerOffline, ex);
|
||||
}
|
||||
|
||||
// Windows API so has to be a real FS
|
||||
await using var fileStream = new FileStream(outputFile, FileMode.CreateNew);
|
||||
|
||||
await Task.Factory.StartNew(
|
||||
|
||||
@@ -96,41 +96,41 @@
|
||||
<!-- Usage: .env file parsing -->
|
||||
<PackageReference Include="DotEnv.Core" Version="3.1.0" />
|
||||
<!-- Usage: Text formatter for Elasticsearch logging plugin -->
|
||||
<PackageReference Include="Elastic.CommonSchema.Serilog" Version="8.12.3" />
|
||||
<PackageReference Include="Elastic.CommonSchema.Serilog" Version="8.18.2" />
|
||||
<!-- Usage: GraphQL API Engine -->
|
||||
<PackageReference Include="HotChocolate.AspNetCore" Version="15.1.3" />
|
||||
<PackageReference Include="HotChocolate.AspNetCore" Version="15.1.8" />
|
||||
<!-- Usage: GraphQL Authorization Plugin -->
|
||||
<PackageReference Include="HotChocolate.AspNetCore.Authorization" Version="15.1.3" />
|
||||
<PackageReference Include="HotChocolate.AspNetCore.Authorization" Version="15.1.8" />
|
||||
<!-- Usage: GraphQL IDatabaseContext support -->
|
||||
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="15.1.3" />
|
||||
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="15.1.8" />
|
||||
<!-- Usage: DataLoader source generation -->
|
||||
<PackageReference Include="HotChocolate.Types.Analyzers" Version="15.1.3" />
|
||||
<PackageReference Include="HotChocolate.Types.Analyzers" Version="15.1.8" />
|
||||
<!-- Usage: GraphQL additional scalar type definitions -->
|
||||
<PackageReference Include="HotChocolate.Types.Scalars" Version="15.1.3" />
|
||||
<PackageReference Include="HotChocolate.Types.Scalars" Version="15.1.8" />
|
||||
<!-- Usage: git interop -->
|
||||
<PackageReference Include="LibGit2Sharp" Version="0.31.0" />
|
||||
<!-- Usage: OpenID Connect support -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.0.18" />
|
||||
<!-- Usage: Support ""legacy"" Newotonsoft.Json in HTTP pipeline. The rest of our codebase uses Newtonsoft. -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.18" />
|
||||
<!-- Usage: Using target JSON serializer for API -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.7" />
|
||||
<!-- Usage: Generating dumps of dotnet engine processes -->
|
||||
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.621003" />
|
||||
<!-- Usage: Database ORM -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.7" />
|
||||
<!-- Usage: Automatic migration generation using command line -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<!-- Usage: Sqlite ORM plugin -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.7" />
|
||||
<!-- Usage: MSSQL ORM plugin -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.7" />
|
||||
<!-- Usage: Database connectivity health check -->
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.15" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.18" />
|
||||
<!-- Usage: OIDC support prerequisite. See https://stackoverflow.com/a/78650835/3976486 -->
|
||||
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.9.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.13.0" />
|
||||
<!-- Usage: POSIX support for syscalls, signals, and symlinks -->
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<!-- Usage: Cron string parsing -->
|
||||
@@ -144,9 +144,9 @@
|
||||
<!-- Usage: Publishing Prometheus metrics -->
|
||||
<PackageReference Include="prometheus-net.AspNetCore.HealthChecks" Version="8.2.1" />
|
||||
<!-- Usage: Discord interop -->
|
||||
<PackageReference Include="Remora.Discord" Version="2025.1.0" />
|
||||
<PackageReference Include="Remora.Discord" Version="2025.2.0" />
|
||||
<!-- Usage: Rich logger builder -->
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.1" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<!-- Usage: Async logging plugin -->
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||
<!-- Usage: Console logging plugin -->
|
||||
@@ -154,15 +154,17 @@
|
||||
<!-- Usage: Elasticsearch logging plugin -->
|
||||
<PackageReference Include="Serilog.Sinks.Elasticsearch" Version="10.0.0" />
|
||||
<!-- Usage: File logging plugin -->
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<!-- Usage: OpenAPI spec generator -->
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
|
||||
<!-- Usage: Newtonsoft.Json plugin for OpenAPI spec generator -->
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="8.1.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="9.0.3" />
|
||||
<!-- Usage: Windows authentication plugin allowing searching for users by name -->
|
||||
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="9.0.4" />
|
||||
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="9.0.7" />
|
||||
<!-- Usage: Filesystem abstraction -->
|
||||
<PackageReference Include="System.IO.Abstractions" Version="22.0.15" />
|
||||
<!-- Usage: Identifying owning user of Windows Process objects -->
|
||||
<PackageReference Include="System.Management" Version="9.0.4" />
|
||||
<PackageReference Include="System.Management" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace Tgstation.Server.Host.Transfer
|
||||
if (downloadProvider.StreamProvider != null)
|
||||
stream = await downloadProvider.StreamProvider(cancellationToken);
|
||||
else
|
||||
stream = ioManager.GetFileStream(downloadProvider.FilePath, downloadProvider.ShareWrite);
|
||||
stream = ioManager.CreateAsyncReadStream(downloadProvider.FilePath, false, downloadProvider.ShareWrite);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ General:
|
||||
HostApiDocumentation: false # Make HTTP API documentation available at /api/doc/tgs_api.json and /api/graphql
|
||||
SkipAddingByondFirewallException: false # Windows Only: Prevent running netsh.exe to add a firewall exception for installed engine binaries
|
||||
DeploymentDirectoryCopyTasksPerCore: 100 # Maximum number of concurrent file copy operations PER available CPU core
|
||||
ByondZipDownloadTemplate: https://www.byond.com/download/build/${Major}/${Major}.${Minor}_byond${Linux:_linux}.zip # Template for downloading official byond versions
|
||||
OpenDreamGitUrl: https://github.com/OpenDreamProject/OpenDream # The repository to retrieve OpenDream from
|
||||
OpenDreamGitTagPrefix: v # The prefix to the OpenDream semver as tags appear in the git repository
|
||||
OpenDreamSuppressInstallOutput: false # Suppress the dotnet output of creating an OpenDream installation. Known to cause hangs in CI.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: JWT injection into HTTP pipeline -->
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.18" />
|
||||
<!-- Usage: GitHub.com interop -->
|
||||
<PackageReference Include="Octokit" Version="14.0.0" />
|
||||
<!-- Usage: YAML conversion of Version objects -->
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.7" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class TestByondInstallerBase
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestUrlTemplateFormatting()
|
||||
{
|
||||
const string OSMarker = "TempleOS";
|
||||
|
||||
Assert.AreEqual(
|
||||
new Uri("https://example.com/$515.1111_Hello Worl$d.zip"),
|
||||
ByondInstallerBase.GetDownloadZipUrl(
|
||||
new Version(515, 1111),
|
||||
"https://example.com/$$${Major}.${Minor}_${TempleOS:Hello Worl$$d}.zip${Linux:Not this}${Or This}",
|
||||
OSMarker));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
@@ -9,6 +10,7 @@ using System.Threading.Tasks;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
@@ -19,16 +21,18 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
[TestMethod]
|
||||
public void TestConstruction()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(null, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(null, null, null, null, null));
|
||||
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, null, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, null, null, null, null));
|
||||
var mockIOManager = new Mock<IIOManager>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, null, null, null));
|
||||
var mockFileDownloader = Mock.Of<IFileDownloader>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, null, null));
|
||||
var mockOptions = Mock.Of<IOptionsMonitor<GeneralConfiguration>>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockOptions, null));
|
||||
|
||||
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
|
||||
_ = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
|
||||
_ = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockOptions, mockLogger.Object);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -38,7 +42,8 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
var mockIOManager = new Mock<IIOManager>();
|
||||
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
|
||||
var mockFileDownloader = Mock.Of<IFileDownloader>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
|
||||
var mockOptions = Mock.Of<IOptionsMonitor<GeneralConfiguration>>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockOptions, mockLogger.Object);
|
||||
await installer.CleanCache(default);
|
||||
}
|
||||
|
||||
@@ -50,7 +55,14 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
|
||||
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
|
||||
var mockFileDownloader = new Mock<IFileDownloader>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader.Object, mockLogger.Object);
|
||||
var mockOptions = new Mock<IOptionsMonitor<GeneralConfiguration>>();
|
||||
const string TestUrl = "https://chumb.is";
|
||||
mockOptions.SetupGet(x => x.CurrentValue).Returns(new GeneralConfiguration
|
||||
{
|
||||
ByondZipDownloadTemplate = TestUrl,
|
||||
});
|
||||
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader.Object, mockOptions.Object, mockLogger.Object);
|
||||
|
||||
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => installer.DownloadVersion(null, null, default).AsTask());
|
||||
|
||||
@@ -58,7 +70,7 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
mockFileDownloader
|
||||
.Setup(
|
||||
x => x.DownloadFile(
|
||||
It.Is<Uri>(uri => uri == new Uri("https://www.byond.com/download/build/511/511.1385_byond_linux.zip")),
|
||||
It.Is<Uri>(uri => uri == new Uri(TestUrl)),
|
||||
null))
|
||||
.Returns(
|
||||
new BufferedFileStreamProvider(
|
||||
@@ -87,7 +99,8 @@ namespace Tgstation.Server.Host.Components.Engine.Tests
|
||||
var mockPostWriteHandler = new Mock<IPostWriteHandler>();
|
||||
var mockLogger = new Mock<ILogger<PosixByondInstaller>>();
|
||||
var mockFileDownloader = Mock.Of<IFileDownloader>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockLogger.Object);
|
||||
var mockOptions = Mock.Of<IOptionsMonitor<GeneralConfiguration>>();
|
||||
var installer = new PosixByondInstaller(mockPostWriteHandler.Object, mockIOManager.Object, mockFileDownloader, mockOptions, mockLogger.Object);
|
||||
|
||||
const string FakePath = "fake";
|
||||
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => installer.Install(null, null, false, default).AsTask());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -31,9 +32,10 @@ namespace Tgstation.Server.Host.Components.StaticFiles.Tests
|
||||
builder.SetMinimumLevel(LogLevel.Trace);
|
||||
});
|
||||
|
||||
var mockFs = new MockFileSystem();
|
||||
var tempPath = Path.GetTempFileName();
|
||||
File.Delete(tempPath);
|
||||
var ioManager = new ResolvingIOManager(new DefaultIOManager(), tempPath);
|
||||
var ioManager = new DefaultIOManager(mockFs).CreateResolverForSubdirectory(tempPath);
|
||||
await ioManager.CreateDirectory(".", CancellationToken.None);
|
||||
try
|
||||
{
|
||||
@@ -46,7 +48,9 @@ namespace Tgstation.Server.Host.Components.StaticFiles.Tests
|
||||
|
||||
var configuration = new Configuration(
|
||||
ioManager,
|
||||
new SynchronousIOManager(loggerFactory.CreateLogger<SynchronousIOManager>()),
|
||||
new SynchronousIOManager(
|
||||
mockFs,
|
||||
loggerFactory.CreateLogger<SynchronousIOManager>()),
|
||||
Mock.Of<IFilesystemLinkFactory>(),
|
||||
Mock.Of<IProcessExecutor>(),
|
||||
Mock.Of<IPostWriteHandler>(),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
@@ -17,10 +18,11 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
[ClassInitialize]
|
||||
public static void SelectFactory(TestContext _)
|
||||
{
|
||||
var fileSystem = new FileSystem();
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
linkFactory = new WindowsFilesystemLinkFactory();
|
||||
linkFactory = new WindowsFilesystemLinkFactory(fileSystem);
|
||||
else
|
||||
linkFactory = new PosixFilesystemLinkFactory();
|
||||
linkFactory = new PosixFilesystemLinkFactory(fileSystem);
|
||||
}
|
||||
|
||||
public static bool HasPermissionToMakeSymlinks()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -14,27 +16,34 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
[TestClass]
|
||||
public sealed class TestIOManager
|
||||
{
|
||||
readonly IIOManager ioManager = new DefaultIOManager();
|
||||
readonly IFileSystem fileSystem;
|
||||
readonly IIOManager ioManager;
|
||||
|
||||
public TestIOManager()
|
||||
{
|
||||
fileSystem = new MockFileSystem();
|
||||
ioManager = new DefaultIOManager(fileSystem);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestDeleteDirectory()
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
File.Delete(tempPath);
|
||||
Directory.CreateDirectory(tempPath);
|
||||
var tempPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName());
|
||||
fileSystem.File.Delete(tempPath);
|
||||
fileSystem.Directory.CreateDirectory(tempPath);
|
||||
try
|
||||
{
|
||||
await File.WriteAllTextAsync(Path.Combine(tempPath, "file.txt"), "asdf");
|
||||
var subDir = Path.Combine(tempPath, "subdir");
|
||||
Directory.CreateDirectory(subDir);
|
||||
await File.WriteAllTextAsync(Path.Combine(subDir, "file2.txt"), "fdsa");
|
||||
await fileSystem.File.WriteAllTextAsync(Path.Combine(tempPath, "file.txt"), "asdf");
|
||||
var subDir = fileSystem.Path.Combine(tempPath, "subdir");
|
||||
fileSystem.Directory.CreateDirectory(subDir);
|
||||
await fileSystem.File.WriteAllTextAsync(Path.Combine(subDir, "file2.txt"), "fdsa");
|
||||
await ioManager.DeleteDirectory(tempPath, default);
|
||||
|
||||
Assert.IsFalse(Directory.Exists(tempPath));
|
||||
Assert.IsFalse(fileSystem.Directory.Exists(tempPath));
|
||||
}
|
||||
catch
|
||||
{
|
||||
Directory.Delete(tempPath, true);
|
||||
fileSystem.Directory.Delete(tempPath, true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -42,13 +51,17 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
[TestMethod]
|
||||
public async Task TestDeleteDirectoryWithSymlinkInsideDoesntRecurse()
|
||||
{
|
||||
var linkFactory = (IFilesystemLinkFactory)(new PlatformIdentifier().IsWindows
|
||||
? new WindowsFilesystemLinkFactory()
|
||||
: new PosixFilesystemLinkFactory());
|
||||
// need a real FS here
|
||||
var fileSystem = new FileSystem();
|
||||
var ioManager = new DefaultIOManager(fileSystem);
|
||||
|
||||
var tempPath = Path.GetTempFileName();
|
||||
File.Delete(tempPath);
|
||||
Directory.CreateDirectory(tempPath);
|
||||
var linkFactory = (IFilesystemLinkFactory)(new PlatformIdentifier().IsWindows
|
||||
? new WindowsFilesystemLinkFactory(fileSystem)
|
||||
: new PosixFilesystemLinkFactory(fileSystem));
|
||||
|
||||
var tempPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName());
|
||||
fileSystem.File.Delete(tempPath);
|
||||
fileSystem.Directory.CreateDirectory(tempPath);
|
||||
try
|
||||
{
|
||||
var targetDir = ioManager.ConcatPath(tempPath, "targetdir");
|
||||
@@ -81,7 +94,7 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
}
|
||||
catch
|
||||
{
|
||||
Directory.Delete(tempPath, true);
|
||||
fileSystem.Directory.Delete(tempPath, true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -89,14 +102,15 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
[TestMethod]
|
||||
public async Task TestFileExists()
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
var tempPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName());
|
||||
await fileSystem.File.WriteAllBytesAsync(tempPath, Array.Empty<byte>());
|
||||
try
|
||||
{
|
||||
Assert.IsTrue(await ioManager.FileExists(tempPath, default));
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempPath);
|
||||
fileSystem.File.Delete(tempPath);
|
||||
}
|
||||
|
||||
Assert.IsFalse(await ioManager.FileExists(tempPath, default));
|
||||
@@ -105,12 +119,12 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
[TestMethod]
|
||||
public async Task TestDirectoryExists()
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
File.Delete(tempPath);
|
||||
var tempPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName());
|
||||
fileSystem.File.Delete(tempPath);
|
||||
|
||||
Assert.IsFalse(await ioManager.DirectoryExists(tempPath, default));
|
||||
|
||||
Directory.CreateDirectory(tempPath);
|
||||
fileSystem.Directory.CreateDirectory(tempPath);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -118,7 +132,7 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
}
|
||||
catch
|
||||
{
|
||||
Directory.Delete(tempPath);
|
||||
fileSystem.Directory.Delete(tempPath);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -180,18 +194,18 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
|
||||
async Task TestCopyDirectory(int? throttle)
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
File.Delete(tempPath);
|
||||
Directory.CreateDirectory(tempPath);
|
||||
var tempPath = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName());
|
||||
fileSystem.File.Delete(tempPath);
|
||||
fileSystem.Directory.CreateDirectory(tempPath);
|
||||
try
|
||||
{
|
||||
var tempPath2 = Path.GetTempFileName();
|
||||
File.Delete(tempPath2);
|
||||
var tempPath2 = fileSystem.Path.Combine(fileSystem.Path.GetTempPath(), fileSystem.Path.GetRandomFileName());
|
||||
fileSystem.File.Delete(tempPath2);
|
||||
|
||||
await File.WriteAllTextAsync(Path.Combine(tempPath, "file.txt"), "asdf");
|
||||
var subDir = Path.Combine(tempPath, "subdir");
|
||||
Directory.CreateDirectory(subDir);
|
||||
await File.WriteAllTextAsync(Path.Combine(subDir, "file2.txt"), "fdsa");
|
||||
await fileSystem.File.WriteAllTextAsync(fileSystem.Path.Combine(tempPath, "file.txt"), "asdf");
|
||||
var subDir = fileSystem.Path.Combine(tempPath, "subdir");
|
||||
fileSystem.Directory.CreateDirectory(subDir);
|
||||
await fileSystem.File.WriteAllTextAsync(fileSystem.Path.Combine(subDir, "file2.txt"), "fdsa");
|
||||
|
||||
try
|
||||
{
|
||||
@@ -203,26 +217,26 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
throttle,
|
||||
default);
|
||||
|
||||
Assert.IsTrue(Directory.Exists(tempPath2));
|
||||
var newFilePath = Path.Combine(tempPath2, "file.txt");
|
||||
Assert.IsTrue(File.Exists(newFilePath));
|
||||
var newFileText = await File.ReadAllTextAsync(newFilePath);
|
||||
Assert.IsTrue(fileSystem.Directory.Exists(tempPath2));
|
||||
var newFilePath = fileSystem.Path.Combine(tempPath2, "file.txt");
|
||||
Assert.IsTrue(fileSystem.File.Exists(newFilePath));
|
||||
var newFileText = await fileSystem.File.ReadAllTextAsync(newFilePath);
|
||||
Assert.AreEqual("asdf", newFileText);
|
||||
var newDirPath = Path.Combine(tempPath2, "subdir");
|
||||
Assert.IsTrue(Directory.Exists(newDirPath));
|
||||
var newFile2Path = Path.Combine(newDirPath, "file2.txt");
|
||||
Assert.IsTrue(File.Exists(newFile2Path));
|
||||
var newFile2Text = await File.ReadAllTextAsync(newFile2Path);
|
||||
var newDirPath = fileSystem.Path.Combine(tempPath2, "subdir");
|
||||
Assert.IsTrue(fileSystem.Directory.Exists(newDirPath));
|
||||
var newFile2Path = fileSystem.Path.Combine(newDirPath, "file2.txt");
|
||||
Assert.IsTrue(fileSystem.File.Exists(newFile2Path));
|
||||
var newFile2Text = await fileSystem.File.ReadAllTextAsync(newFile2Path);
|
||||
Assert.AreEqual("fdsa", newFile2Text);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempPath2, true);
|
||||
fileSystem.Directory.Delete(tempPath2, true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempPath, true);
|
||||
fileSystem.Directory.Delete(tempPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -58,7 +59,8 @@ namespace Tgstation.Server.Host.System.Tests
|
||||
processExecutor = new ProcessExecutor(
|
||||
new PosixProcessFeatures(
|
||||
new Lazy<IProcessExecutor>(() => processExecutor),
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(
|
||||
new FileSystem()),
|
||||
loggerFactory.CreateLogger<PosixProcessFeatures>()),
|
||||
Mock.Of<IIOManager>(),
|
||||
loggerFactory.CreateLogger<ProcessExecutor>(),
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Tgstation.Server.Host.IO;
|
||||
@@ -21,7 +22,7 @@ namespace Tgstation.Server.Host.System.Tests
|
||||
{
|
||||
features = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? (IProcessFeatures)new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
|
||||
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => null), new DefaultIOManager(), Mock.Of<ILogger<PosixProcessFeatures>>());
|
||||
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => null), new DefaultIOManager(new MockFileSystem()), Mock.Of<ILogger<PosixProcessFeatures>>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -13,8 +14,10 @@ namespace Tgstation.Server.Host.System.Tests
|
||||
public sealed class TestSymlinkFactory
|
||||
{
|
||||
readonly IFilesystemLinkFactory factory = new PlatformIdentifier().IsWindows
|
||||
? new WindowsFilesystemLinkFactory()
|
||||
: new PosixFilesystemLinkFactory();
|
||||
? new WindowsFilesystemLinkFactory(
|
||||
new FileSystem())
|
||||
: new PosixFilesystemLinkFactory(
|
||||
new FileSystem());
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestSymlinks()
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Moq;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -80,28 +81,29 @@ namespace Tgstation.Server.Host.Tests
|
||||
public async Task TestStandardRunWithExceptionAndWatchdog()
|
||||
{
|
||||
var mockServer = new Mock<IServer>();
|
||||
var mockFs = new MockFileSystem();
|
||||
var exception = new DivideByZeroException();
|
||||
mockServer.Setup(x => x.Run(It.IsAny<CancellationToken>())).Throws(exception);
|
||||
mockServer.SetupGet(x => x.RestartRequested).Returns(true);
|
||||
var mockServerFactory = new Mock<IServerFactory>();
|
||||
mockServerFactory.SetupGet(x => x.IOManager).Returns(new DefaultIOManager());
|
||||
mockServerFactory.SetupGet(x => x.IOManager).Returns(new DefaultIOManager(mockFs));
|
||||
mockServerFactory.Setup(x => x.CreateServer(It.IsNotNull<string[]>(), It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(mockServer.Object);
|
||||
var program = new Program
|
||||
{
|
||||
ServerFactory = mockServerFactory.Object
|
||||
};
|
||||
|
||||
var tempFileName = Path.GetTempFileName();
|
||||
File.Delete(tempFileName);
|
||||
var tempFileName = mockFs.Path.Combine(mockFs.Path.GetTempPath(), mockFs.Path.GetRandomFileName());
|
||||
mockFs.File.Delete(tempFileName);
|
||||
try
|
||||
{
|
||||
var result = await program.Main(Array.Empty<string>(), tempFileName);
|
||||
Assert.AreEqual(HostExitCode.Error, result);
|
||||
Assert.AreEqual(exception.ToString(), File.ReadAllText(tempFileName));
|
||||
Assert.AreEqual(exception.ToString(), mockFs.File.ReadAllText(tempFileName));
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(tempFileName);
|
||||
mockFs.File.Delete(tempFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
using System;
|
||||
using System.IO.Abstractions.TestingHelpers;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Tgstation.Server.Host.Core;
|
||||
@@ -20,11 +21,13 @@ namespace Tgstation.Server.Host.Tests
|
||||
[TestMethod]
|
||||
public void TestConstructor()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new ServerFactory(null, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new ServerFactory(null, null, null));
|
||||
IAssemblyInformationProvider assemblyInformationProvider = Mock.Of<IAssemblyInformationProvider>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new ServerFactory(assemblyInformationProvider, null));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new ServerFactory(assemblyInformationProvider, null, null));
|
||||
IIOManager ioManager = Mock.Of<IIOManager>();
|
||||
_ = new ServerFactory(assemblyInformationProvider, ioManager);
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new ServerFactory(assemblyInformationProvider, ioManager, null));
|
||||
var mockFileSystem = new MockFileSystem();
|
||||
_ = new ServerFactory(assemblyInformationProvider, ioManager, mockFileSystem);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Usage: Creating mock database implementations -->
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.7" />
|
||||
<!-- Usage: Creating mock filesystem implementations -->
|
||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="22.0.15" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -12,6 +13,8 @@ using Moq;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Common.Http;
|
||||
using Tgstation.Server.Host.Components.Engine;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.System;
|
||||
@@ -44,9 +47,9 @@ namespace Tgstation.Server.Tests
|
||||
var logger = loggerFactory.CreateLogger("CachingFileDownloader");
|
||||
|
||||
var cfd = new CachingFileDownloader(loggerFactory.CreateLogger<CachingFileDownloader>());
|
||||
var edgeVersion = await EngineTest.GetEdgeVersion(Api.Models.EngineType.Byond, cfd, cancellationToken);
|
||||
|
||||
await InitializeByondVersion(logger, edgeVersion.Version, new PlatformIdentifier().IsWindows, cancellationToken);
|
||||
// this also will inject the edge version
|
||||
var edgeVersion = await EngineTest.GetEdgeVersion(Api.Models.EngineType.Byond, logger, cfd, cancellationToken);
|
||||
|
||||
// predownload the target github release update asset
|
||||
var gitHubToken = Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN");
|
||||
@@ -78,7 +81,7 @@ namespace Tgstation.Server.Tests
|
||||
ServiceCollectionExtensions.UseFileDownloader<CachingFileDownloader>();
|
||||
}
|
||||
|
||||
public static async ValueTask InitializeByondVersion(ILogger logger, Version byondVersion, bool windows, CancellationToken cancellationToken)
|
||||
public static async ValueTask InitializeByondVersion(ILogger logger, Version byondVersion, bool windows, CancellationToken cancellationToken, string urlCacheOverrideTemplate = null)
|
||||
{
|
||||
var version = new EngineVersion
|
||||
{
|
||||
@@ -86,25 +89,50 @@ namespace Tgstation.Server.Tests
|
||||
Version = byondVersion,
|
||||
};
|
||||
|
||||
var url = new Uri(
|
||||
$"https://www.byond.com/download/build/{version.Version.Major}/{version.Version.Major}.{version.Version.Minor}_byond{(!windows ? "_linux" : string.Empty)}.zip");
|
||||
string path = null;
|
||||
if (TestingUtils.RunningInGitHubActions)
|
||||
{
|
||||
// actions is supposed to cache BYOND for us
|
||||
var urlTemplate = TestingUtils.ByondZipDownloadTemplate;
|
||||
|
||||
var dir = Path.Combine(
|
||||
var url = ByondInstallerBase.GetDownloadZipUrl(byondVersion, urlTemplate, new PlatformIdentifier().IsWindows ? "Windows" : "Linux");
|
||||
string path = null;
|
||||
string basePath = Environment.GetEnvironmentVariable("TGS_TEST_BYOND_ZIPS_BASE_PATH");
|
||||
if (basePath == null && TestingUtils.RunningInGitHubActions)
|
||||
{
|
||||
// actions is supposed to cache BYOND for us here
|
||||
basePath = Path.Combine(
|
||||
Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.UserProfile,
|
||||
Environment.SpecialFolderOption.DoNotVerify),
|
||||
"byond-zips-cache",
|
||||
"byond-zips-cache");
|
||||
}
|
||||
|
||||
if (basePath != null)
|
||||
{
|
||||
var dir = Path.Combine(
|
||||
basePath,
|
||||
"live",
|
||||
windows ? "windows" : "linux");
|
||||
path = Path.Combine(
|
||||
dir,
|
||||
$"{version.Version.Major}.{version.Version.Minor}",
|
||||
$"{version.Version.Major}.{version.Version.Minor}.zip");
|
||||
}
|
||||
|
||||
await (await CacheFile(logger, url, null, path, cancellationToken)).DisposeAsync();
|
||||
Uri overrideUrl = null;
|
||||
if (urlCacheOverrideTemplate != null)
|
||||
{
|
||||
overrideUrl = url;
|
||||
url = ByondInstallerBase.GetDownloadZipUrl(byondVersion, urlCacheOverrideTemplate, new PlatformIdentifier().IsWindows ? "Windows" : "Linux");
|
||||
}
|
||||
|
||||
await (await CacheFile(
|
||||
logger,
|
||||
url,
|
||||
null,
|
||||
path,
|
||||
cancellationToken))
|
||||
.DisposeAsync();
|
||||
|
||||
if (overrideUrl != null)
|
||||
cachedPaths[overrideUrl.ToString()] = cachedPaths[url.ToString()];
|
||||
}
|
||||
|
||||
public static void Cleanup()
|
||||
@@ -195,7 +223,7 @@ namespace Tgstation.Server.Tests
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
await using var fs = new DefaultIOManager().CreateAsyncSequentialWriteStream(path);
|
||||
await using var fs = new DefaultIOManager(new FileSystem()).CreateAsyncSequentialWriteStream(path);
|
||||
await ms.CopyToAsync(fs, cancellationToken);
|
||||
|
||||
cachedPaths.Add(url.ToString(), Tuple.Create(path, temporal));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
@@ -90,7 +91,8 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
public ValueTask SetupDMApiTests(bool includingRoot, CancellationToken cancellationToken)
|
||||
{
|
||||
// just use an I/O manager here
|
||||
var ioManager = new DefaultIOManager();
|
||||
var ioManager = new DefaultIOManager(
|
||||
new FileSystem());
|
||||
|
||||
async ValueTask TestStaticFileAndDir()
|
||||
{
|
||||
@@ -127,7 +129,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
Path = $"/EventScripts/{scriptName}"
|
||||
};
|
||||
|
||||
await using var readStream = ioManager.GetFileStream($"../../../../DMAPI/{(basic ? "BasicOperation" : "LongRunning")}/{scriptName}", false);
|
||||
await using var readStream = ioManager.CreateAsyncReadStream($"../../../../DMAPI/{(basic ? "BasicOperation" : "LongRunning")}/{scriptName}", true, false);
|
||||
await configurationClient.Write(
|
||||
resourcingScript,
|
||||
readStream,
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
EngineVersion testVersion;
|
||||
readonly EngineType testEngine = engineType;
|
||||
|
||||
public Task Run(CancellationToken cancellationToken, out Task firstInstall)
|
||||
public Task Run(ILogger logger, CancellationToken cancellationToken, out Task firstInstall)
|
||||
{
|
||||
firstInstall = RunPartOne(cancellationToken);
|
||||
firstInstall = RunPartOne(logger, cancellationToken);
|
||||
return RunContinued(firstInstall, cancellationToken);
|
||||
}
|
||||
|
||||
public static async ValueTask<EngineVersion> GetEdgeVersion(EngineType engineType, IFileDownloader fileDownloader, CancellationToken cancellationToken)
|
||||
public static async ValueTask<EngineVersion> GetEdgeVersion(EngineType engineType, ILogger logger, IFileDownloader fileDownloader, CancellationToken cancellationToken)
|
||||
{
|
||||
var edgeVersion = edgeVersions[engineType];
|
||||
|
||||
@@ -57,26 +57,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
EngineVersion engineVersion;
|
||||
if (engineType == EngineType.Byond)
|
||||
{
|
||||
await using var provider = fileDownloader.DownloadFile(new Uri("https://www.byond.com/download/version.txt"), null);
|
||||
var stream = await provider.GetResult(cancellationToken);
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8, false, -1, true);
|
||||
var text = await reader.ReadToEndAsync(cancellationToken);
|
||||
var splits = text.Split('\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var targetVersion = splits.Last();
|
||||
|
||||
var badVersionMap = new PlatformIdentifier().IsWindows
|
||||
? []
|
||||
// linux map also needs updating in CI
|
||||
: new Dictionary<string, string>()
|
||||
{
|
||||
{ "515.1612", "515.1611" }
|
||||
};
|
||||
|
||||
badVersionMap.Add("515.1617", "515.1616");
|
||||
|
||||
if (badVersionMap.TryGetValue(targetVersion, out var remappedVersion))
|
||||
targetVersion = remappedVersion;
|
||||
var targetVersion = await TestingUtils.GetByondEdgeVersion(logger, fileDownloader, cancellationToken);
|
||||
|
||||
Assert.IsTrue(EngineVersion.TryParse(targetVersion, out engineVersion), $"Bad version: {targetVersion}");
|
||||
}
|
||||
@@ -112,9 +93,9 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
return edgeVersions[engineType] = engineVersion;
|
||||
}
|
||||
|
||||
async Task RunPartOne(CancellationToken cancellationToken)
|
||||
async Task RunPartOne(ILogger logger, CancellationToken cancellationToken)
|
||||
{
|
||||
testVersion = await GetEdgeVersion(testEngine, fileDownloader, cancellationToken);
|
||||
testVersion = await GetEdgeVersion(testEngine, logger, fileDownloader, cancellationToken);
|
||||
await TestNoVersion(cancellationToken);
|
||||
await TestInstallNullVersion(cancellationToken);
|
||||
await TestInstallStable(cancellationToken);
|
||||
@@ -284,8 +265,11 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
|
||||
async Task TestCustomInstalls(CancellationToken cancellationToken)
|
||||
{
|
||||
var generalConfigOptionsMock = new Mock<IOptions<GeneralConfiguration>>();
|
||||
generalConfigOptionsMock.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
|
||||
var generalConfigOptionsMock = new Mock<IOptionsMonitor<GeneralConfiguration>>();
|
||||
generalConfigOptionsMock.SetupGet(x => x.CurrentValue).Returns(new GeneralConfiguration
|
||||
{
|
||||
ByondZipDownloadTemplate = TestingUtils.ByondZipDownloadTemplate,
|
||||
});
|
||||
var sessionConfigOptionsMock = new Mock<IOptions<SessionConfiguration>>();
|
||||
sessionConfigOptionsMock.SetupGet(x => x.Value).Returns(new SessionConfiguration());
|
||||
|
||||
@@ -303,6 +287,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
Mock.Of<IPostWriteHandler>(),
|
||||
Mock.Of<IIOManager>(),
|
||||
fileDownloader,
|
||||
generalConfigOptionsMock.Object,
|
||||
Mock.Of<ILogger<PosixByondInstaller>>());
|
||||
|
||||
using var windowsByondInstaller = byondInstaller as WindowsByondInstaller;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -36,6 +37,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
readonly ushort serverPort = serverPort;
|
||||
|
||||
public async Task RunTests(
|
||||
ILogger logger,
|
||||
IInstanceClient instanceClient,
|
||||
ushort dmPort,
|
||||
ushort ddPort,
|
||||
@@ -44,14 +46,14 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
bool usingBasicWatchdog,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var testVersion = await EngineTest.GetEdgeVersion(EngineType.Byond, fileDownloader, cancellationToken);
|
||||
var testVersion = await EngineTest.GetEdgeVersion(EngineType.Byond, logger, fileDownloader, cancellationToken);
|
||||
await using var engineTest = new EngineTest(instanceClient.Engine, instanceClient.Jobs, fileDownloader, instanceClient.Metadata, testVersion.Engine.Value);
|
||||
await using var chatTest = new ChatTest(instanceClient.ChatBots, instanceManagerClient, instanceClient.Jobs, instanceClient.Metadata);
|
||||
var configTest = new ConfigurationTest(instanceClient.Configuration, instanceClient.Metadata);
|
||||
await using var repoTest = new RepositoryTest(instanceClient, instanceClient.Repository, instanceClient.Jobs);
|
||||
await using var dmTest = new DeploymentTest(instanceClient, instanceClient.Jobs, dmPort, ddPort, lowPrioDeployment, testVersion);
|
||||
|
||||
var byondTask = engineTest.Run(cancellationToken, out var firstInstall);
|
||||
var byondTask = engineTest.Run(logger, cancellationToken, out var firstInstall);
|
||||
var chatTask = chatTest.RunPreWatchdog(cancellationToken);
|
||||
|
||||
var repoLongJob = await repoTest.RunLongClone(cancellationToken);
|
||||
@@ -89,25 +91,26 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
Uri openDreamUrl,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var ioManager = new DefaultIOManager();
|
||||
var ioManager = new DefaultIOManager(new FileSystem());
|
||||
var odRepoDir = ioManager.ConcatPath(
|
||||
Environment.GetFolderPath(
|
||||
Environment.SpecialFolder.LocalApplicationData,
|
||||
Environment.SpecialFolderOption.DoNotVerify),
|
||||
new AssemblyInformationProvider().VersionPrefix,
|
||||
"OpenDreamRepository");
|
||||
var odRepoIoManager = new ResolvingIOManager(ioManager, odRepoDir);
|
||||
var odRepoIoManager = ioManager.CreateResolverForSubdirectory(odRepoDir);
|
||||
|
||||
var mockOptions = new Mock<IOptions<GeneralConfiguration>>();
|
||||
var mockOptionsMonitor = new Mock<IOptionsMonitor<GeneralConfiguration>>();
|
||||
var genConfig = new GeneralConfiguration
|
||||
{
|
||||
OpenDreamGitUrl = openDreamUrl,
|
||||
ByondZipDownloadTemplate = TestingUtils.ByondZipDownloadTemplate,
|
||||
};
|
||||
mockOptions.SetupGet(x => x.Value).Returns(genConfig);
|
||||
mockOptionsMonitor.SetupGet(x => x.CurrentValue).Returns(genConfig);
|
||||
IEngineInstaller byondInstaller =
|
||||
compatVersion.Engine == EngineType.OpenDream
|
||||
? new OpenDreamInstaller(
|
||||
new DefaultIOManager(),
|
||||
ioManager,
|
||||
Mock.Of<ILogger<OpenDreamInstaller>>(),
|
||||
new PlatformIdentifier(),
|
||||
Mock.Of<IProcessExecutor>(),
|
||||
@@ -124,20 +127,21 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
genConfig),
|
||||
Mock.Of<IAsyncDelayer>(),
|
||||
Mock.Of<IAbstractHttpClientFactory>(),
|
||||
mockOptions.Object,
|
||||
Options.Create(genConfig),
|
||||
Options.Create(new SessionConfiguration()))
|
||||
: new PlatformIdentifier().IsWindows
|
||||
? new WindowsByondInstaller(
|
||||
Mock.Of<IProcessExecutor>(),
|
||||
Mock.Of<IIOManager>(),
|
||||
fileDownloader,
|
||||
Options.Create(genConfig),
|
||||
mockOptionsMonitor.Object,
|
||||
Options.Create(new SessionConfiguration()),
|
||||
Mock.Of<ILogger<WindowsByondInstaller>>())
|
||||
: new PosixByondInstaller(
|
||||
Mock.Of<IPostWriteHandler>(),
|
||||
Mock.Of<IIOManager>(),
|
||||
fileDownloader,
|
||||
mockOptionsMonitor.Object,
|
||||
Mock.Of<ILogger<PosixByondInstaller>>());
|
||||
|
||||
using var windowsByondInstaller = byondInstaller as WindowsByondInstaller;
|
||||
|
||||
@@ -14,6 +14,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
@@ -815,7 +816,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
|
||||
var features = new PosixProcessFeatures(
|
||||
new Lazy<IProcessExecutor>(Mock.Of<IProcessExecutor>()),
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(new FileSystem()),
|
||||
Mock.Of<ILogger<PosixProcessFeatures>>());
|
||||
|
||||
features.SuspendProcess(proc);
|
||||
@@ -882,7 +883,7 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
executor = new ProcessExecutor(
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
|
||||
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => executor), new DefaultIOManager(), Mock.Of<ILogger<PosixProcessFeatures>>()),
|
||||
: new PosixProcessFeatures(new Lazy<IProcessExecutor>(() => executor), new DefaultIOManager(new FileSystem()), Mock.Of<ILogger<PosixProcessFeatures>>()),
|
||||
Mock.Of<IIOManager>(),
|
||||
Mock.Of<ILogger<ProcessExecutor>>(),
|
||||
LoggerFactory.Create(x => { }));
|
||||
|
||||
@@ -6,6 +6,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -43,7 +44,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
for (int i = 0; i < 5; ++i)
|
||||
try
|
||||
{
|
||||
new DefaultIOManager().DeleteDirectory(directory, default).GetAwaiter().GetResult();
|
||||
new DefaultIOManager(new FileSystem()).DeleteDirectory(directory, default).GetAwaiter().GetResult();
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -156,7 +157,8 @@ namespace Tgstation.Server.Tests.Live
|
||||
$"Security:TokenExpiryMinutes=120", // timeouts are useless for us
|
||||
$"General:OpenDreamSuppressInstallOutput={TestingUtils.RunningInGitHubActions}",
|
||||
"Telemetry:DisableVersionReporting=true",
|
||||
$"General:PrometheusPort={port}"
|
||||
$"General:PrometheusPort={port}",
|
||||
$"General:ByondZipDownloadTemplate={TestingUtils.ByondZipDownloadTemplate}"
|
||||
};
|
||||
|
||||
if (MultiServerClient.UseGraphQL)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Net;
|
||||
@@ -415,7 +416,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
var gitHubToken = Environment.GetEnvironmentVariable("TGS_TEST_GITHUB_TOKEN");
|
||||
if (String.IsNullOrWhiteSpace(gitHubToken))
|
||||
gitHubToken = null;
|
||||
await new Host.IO.DefaultIOManager().DeleteDirectory(server.UpdatePath, cancellationToken);
|
||||
await new Host.IO.DefaultIOManager(new FileSystem()).DeleteDirectory(server.UpdatePath, cancellationToken);
|
||||
serverTask = server.Run(cancellationToken).AsTask();
|
||||
|
||||
await using (var adminClient = await CreateAdminClient(server.ApiUrl, cancellationToken))
|
||||
@@ -922,6 +923,12 @@ namespace Tgstation.Server.Tests.Live
|
||||
await using var node1Client = await CreateAdminClient(node1.ApiUrl, cancellationToken);
|
||||
await using var node2Client = await CreateAdminClient(node2.ApiUrl, cancellationToken);
|
||||
|
||||
// test a token signed from any one node will work on another
|
||||
var token = node2Client.RestClient.Token;
|
||||
var testNode1Client = restClientFactory.CreateFromToken(node1.ApiUrl, token);
|
||||
|
||||
await testNode1Client.ServerInformation(cancellationToken);
|
||||
|
||||
var controllerInfo = await controllerClient.RestClient.ServerInformation(cancellationToken);
|
||||
|
||||
async Task WaitForSwarmServerUpdate(IRestServerClient client, int currentServerCount)
|
||||
@@ -1129,7 +1136,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
ApiValidationSecurityLevel = DreamDaemonSecurity.Trusted,
|
||||
}, cancellationToken);
|
||||
|
||||
var ioManager = new Host.IO.DefaultIOManager();
|
||||
var ioManager = new Host.IO.DefaultIOManager(new FileSystem());
|
||||
var repoPath = ioManager.ConcatPath(instance.Path, "Repository");
|
||||
await using var jobsTest = new JobsRequiredTest(instanceClient.Jobs);
|
||||
var postWriteHandler = (Host.IO.IPostWriteHandler)(new PlatformIdentifier().IsWindows
|
||||
@@ -1415,6 +1422,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
await Task.Yield();
|
||||
|
||||
InstanceManager GetInstanceManager() => ((Host.Server)server.RealServer).Host.Services.GetRequiredService<InstanceManager>();
|
||||
ILogger GetLogger() => ((Host.Server)server.RealServer).Host.Services.GetRequiredService<ILogger<TestLiveServer>>();
|
||||
|
||||
// main run
|
||||
var serverTask = server.Run(cancellationToken).AsTask();
|
||||
@@ -1587,7 +1595,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
var testSerialized = TestingUtils.RunningInGitHubActions; // they only have 2 cores, can't handle intense parallelization
|
||||
async Task ODCompatTests()
|
||||
{
|
||||
var edgeODVersionTask = EngineTest.GetEdgeVersion(EngineType.OpenDream, fileDownloader, cancellationToken);
|
||||
var edgeODVersionTask = EngineTest.GetEdgeVersion(EngineType.OpenDream, GetLogger(), fileDownloader, cancellationToken);
|
||||
|
||||
var ex = await Assert.ThrowsExceptionAsync<JobException>(
|
||||
() => InstanceTest.DownloadEngineVersion(
|
||||
@@ -1622,6 +1630,16 @@ namespace Tgstation.Server.Tests.Live
|
||||
if (openDreamOnly)
|
||||
return;
|
||||
|
||||
var windowsMinCompat = new Version(510, 1346);
|
||||
var linuxMinCompat = new Version(512, 1451); // http://www.byond.com/forum/?forum=5&command=search&scope=local&text=resolved%3a512.1451
|
||||
await CachingFileDownloader.InitializeByondVersion(
|
||||
GetLogger(),
|
||||
new PlatformIdentifier().IsWindows
|
||||
? windowsMinCompat
|
||||
: linuxMinCompat,
|
||||
new PlatformIdentifier().IsWindows,
|
||||
cancellationToken);
|
||||
|
||||
var compatTests = FailFast(
|
||||
instanceTest
|
||||
.RunCompatTests(
|
||||
@@ -1629,8 +1647,8 @@ namespace Tgstation.Server.Tests.Live
|
||||
{
|
||||
Engine = EngineType.Byond,
|
||||
Version = new PlatformIdentifier().IsWindows
|
||||
? new Version(510, 1346)
|
||||
: new Version(512, 1451) // http://www.byond.com/forum/?forum=5&command=search&scope=local&text=resolved%3a512.1451
|
||||
? windowsMinCompat
|
||||
: linuxMinCompat,
|
||||
},
|
||||
server.OpenDreamUrl,
|
||||
firstAdminRestClient.Instances.CreateClient(compatInstance),
|
||||
@@ -1646,6 +1664,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
await FailFast(
|
||||
instanceTest
|
||||
.RunTests(
|
||||
GetLogger(),
|
||||
instanceClient,
|
||||
mainDMPort.Value,
|
||||
mainDDPort.Value,
|
||||
@@ -1878,7 +1897,7 @@ namespace Tgstation.Server.Tests.Live
|
||||
preStartupTime = DateTimeOffset.UtcNow;
|
||||
serverTask = server.Run(cancellationToken).AsTask();
|
||||
long expectedCompileJobId, expectedStaged;
|
||||
var edgeVersion = await EngineTest.GetEdgeVersion(EngineType.Byond, fileDownloader, cancellationToken);
|
||||
var edgeVersion = await EngineTest.GetEdgeVersion(EngineType.Byond, GetLogger(), fileDownloader, cancellationToken);
|
||||
await using (var adminClient = await CreateAdminClient(server.ApiUrl, cancellationToken))
|
||||
{
|
||||
var restAdminClient = adminClient.RestClient;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -58,7 +59,7 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
finally
|
||||
{
|
||||
await new DefaultIOManager().DeleteDirectory(
|
||||
await new DefaultIOManager(new FileSystem()).DeleteDirectory(
|
||||
Path.GetDirectoryName(tempPath),
|
||||
CancellationToken.None);
|
||||
}
|
||||
@@ -75,8 +76,7 @@ namespace Tgstation.Server.Tests
|
||||
using var manager = new RepositoryManager(
|
||||
repoFac,
|
||||
commands,
|
||||
new ResolvingIOManager(
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(new FileSystem()).CreateResolverForSubdirectory(
|
||||
tempPath),
|
||||
Mock.Of<IEventConsumer>(),
|
||||
new WindowsPostWriteHandler(),
|
||||
@@ -131,7 +131,7 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
finally
|
||||
{
|
||||
await new DefaultIOManager().DeleteDirectory(
|
||||
await new DefaultIOManager(new FileSystem()).DeleteDirectory(
|
||||
Path.GetDirectoryName(tempPath),
|
||||
CancellationToken.None);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -24,7 +25,7 @@ namespace Tgstation.Server.Tests
|
||||
var platformIdentifier = new PlatformIdentifier();
|
||||
var processExecutor = new ProcessExecutor(
|
||||
Mock.Of<IProcessFeatures>(),
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(new FileSystem()),
|
||||
Mock.Of<ILogger<ProcessExecutor>>(),
|
||||
loggerFactory);
|
||||
|
||||
@@ -52,7 +53,7 @@ namespace Tgstation.Server.Tests
|
||||
var platformIdentifier = new PlatformIdentifier();
|
||||
var processExecutor = new ProcessExecutor(
|
||||
Mock.Of<IProcessFeatures>(),
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(new FileSystem()),
|
||||
loggerFactory.CreateLogger<ProcessExecutor>(),
|
||||
loggerFactory);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ using Tgstation.Server.Host.System;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Tests.Live;
|
||||
using Tgstation.Server.Host.Properties;
|
||||
using System.IO.Abstractions;
|
||||
|
||||
namespace Tgstation.Server.Tests
|
||||
{
|
||||
@@ -103,10 +104,14 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("RequiresDatabase")]
|
||||
public async Task TestDDExeByondVersion()
|
||||
{
|
||||
var mockGeneralConfigurationOptions = new Mock<IOptions<GeneralConfiguration>>();
|
||||
mockGeneralConfigurationOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration());
|
||||
var mockGeneralConfigurationOptions = new Mock<IOptionsMonitor<GeneralConfiguration>>();
|
||||
mockGeneralConfigurationOptions.SetupGet(x => x.CurrentValue).Returns(new GeneralConfiguration
|
||||
{
|
||||
ByondZipDownloadTemplate = TestingUtils.ByondZipDownloadTemplate,
|
||||
});
|
||||
var mockSessionConfigurationOptions = new Mock<IOptions<SessionConfiguration>>();
|
||||
mockSessionConfigurationOptions.SetupGet(x => x.Value).Returns(new SessionConfiguration());
|
||||
|
||||
@@ -165,12 +170,14 @@ namespace Tgstation.Server.Tests
|
||||
static Version MapThreadsVersion() => (Version)typeof(ByondInstallerBase).GetField("MapThreadsVersion", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) ?? throw new InvalidOperationException("Couldn't find MapThreadsVersion");
|
||||
|
||||
[TestMethod]
|
||||
[TestCategory("RequiresDatabase")]
|
||||
public async Task TestMapThreadsByondVersion()
|
||||
{
|
||||
var mockGeneralConfigurationOptions = new Mock<IOptions<GeneralConfiguration>>();
|
||||
mockGeneralConfigurationOptions.SetupGet(x => x.Value).Returns(new GeneralConfiguration
|
||||
var mockGeneralConfigurationOptions = new Mock<IOptionsMonitor<GeneralConfiguration>>();
|
||||
mockGeneralConfigurationOptions.SetupGet(x => x.CurrentValue).Returns(new GeneralConfiguration
|
||||
{
|
||||
SkipAddingByondFirewallException = true,
|
||||
ByondZipDownloadTemplate = TestingUtils.ByondZipDownloadTemplate,
|
||||
});
|
||||
var mockSessionConfigurationOptions = new Mock<IOptions<SessionConfiguration>>();
|
||||
mockSessionConfigurationOptions.SetupGet(x => x.Value).Returns(new SessionConfiguration());
|
||||
@@ -207,8 +214,9 @@ namespace Tgstation.Server.Tests
|
||||
loggerFactory.CreateLogger<WindowsByondInstaller>())
|
||||
: new PosixByondInstaller(
|
||||
new PosixPostWriteHandler(loggerFactory.CreateLogger<PosixPostWriteHandler>()),
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(new FileSystem()),
|
||||
fileDownloader,
|
||||
mockGeneralConfigurationOptions.Object,
|
||||
loggerFactory.CreateLogger<PosixByondInstaller>());
|
||||
using var disposable = byondInstaller as IDisposable;
|
||||
|
||||
@@ -217,13 +225,13 @@ namespace Tgstation.Server.Tests
|
||||
? new WindowsProcessFeatures(Mock.Of<ILogger<WindowsProcessFeatures>>())
|
||||
: new PosixProcessFeatures(
|
||||
new Lazy<IProcessExecutor>(() => null),
|
||||
new DefaultIOManager(),
|
||||
new DefaultIOManager(new FileSystem()),
|
||||
loggerFactory.CreateLogger<PosixProcessFeatures>()),
|
||||
Mock.Of<IIOManager>(),
|
||||
loggerFactory.CreateLogger<ProcessExecutor>(),
|
||||
loggerFactory);
|
||||
|
||||
var ioManager = new DefaultIOManager();
|
||||
var ioManager = new DefaultIOManager(new FileSystem());
|
||||
var tempPath = ioManager.ConcatPath(LiveTestingServer.BaseDirectory, "mapthreads");
|
||||
await ioManager.CreateDirectory(tempPath, default);
|
||||
try
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Moq;
|
||||
|
||||
using Tgstation.Server.Host.Components.Engine;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Tests
|
||||
{
|
||||
@@ -67,8 +75,91 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
finally
|
||||
{
|
||||
await new DefaultIOManager().DeleteDirectory(tempFolder, cancellationToken);
|
||||
await new DefaultIOManager(new FileSystem()).DeleteDirectory(tempFolder, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
static string byondZipDownloadTemplate;
|
||||
|
||||
public static string ByondZipDownloadTemplate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (byondZipDownloadTemplate == null)
|
||||
{
|
||||
var envvar = Environment.GetEnvironmentVariable("TGS_TEST_BYOND_ZIP_DOWNLOAD_TEMPLATE");
|
||||
if (envvar != null)
|
||||
byondZipDownloadTemplate = envvar;
|
||||
else
|
||||
byondZipDownloadTemplate = GeneralConfiguration.DefaultByondZipDownloadTemplate;
|
||||
}
|
||||
|
||||
return byondZipDownloadTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
static string edgeVersion = null;
|
||||
public static async ValueTask<string> GetByondEdgeVersion(ILogger logger, IFileDownloader fileDownloader, CancellationToken cancellationToken)
|
||||
{
|
||||
if (edgeVersion != null)
|
||||
return edgeVersion;
|
||||
|
||||
async ValueTask<string> GetVersionFromResponse(string versionTxt)
|
||||
{
|
||||
await using var provider = fileDownloader.DownloadFile(new Uri(versionTxt), null);
|
||||
var stream = await provider.GetResult(cancellationToken);
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8, false, -1, true);
|
||||
var text = await reader.ReadToEndAsync(cancellationToken);
|
||||
var splits = text.Split('\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
var targetVersion = splits.Last();
|
||||
|
||||
var badVersionMap = new PlatformIdentifier().IsWindows
|
||||
? []
|
||||
// linux map also needs updating in CI
|
||||
: new Dictionary<string, string>()
|
||||
{
|
||||
{ "515.1612", "515.1611" }
|
||||
};
|
||||
|
||||
badVersionMap.Add("515.1617", "515.1616");
|
||||
|
||||
if (badVersionMap.TryGetValue(targetVersion, out var remappedVersion))
|
||||
targetVersion = remappedVersion;
|
||||
|
||||
return targetVersion;
|
||||
}
|
||||
|
||||
var mirroredVersionTxt = Environment.GetEnvironmentVariable("TGS_TEST_BYOND_MIRROR_VERSION_TXT");
|
||||
try
|
||||
{
|
||||
// always check byond.com first for latest up-to-date, mirror should ALWAYS have stable versions
|
||||
// except byond hates all CI runners now
|
||||
const string DefaultMirror = "https://spacestation13.github.io/byond-builds/version.txt";
|
||||
edgeVersion = await GetVersionFromResponse(DefaultMirror);
|
||||
|
||||
logger.LogInformation("Downloading edge version from SS13 mirror {edge}", edgeVersion);
|
||||
|
||||
// if we got the result from byond.com, make sure the cache grabs the zip from there as well
|
||||
await CachingFileDownloader.InitializeByondVersion(
|
||||
logger,
|
||||
Version.Parse(edgeVersion),
|
||||
new PlatformIdentifier().IsWindows,
|
||||
cancellationToken,
|
||||
"https://spacestation13.github.io/byond-builds/${Major}/${Major}.${Minor}_byond${Linux:_linux}.zip");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Cannot download zip from byond.com!");
|
||||
if (ByondZipDownloadTemplate == GeneralConfiguration.DefaultByondZipDownloadTemplate || mirroredVersionTxt == null)
|
||||
throw;
|
||||
|
||||
// fall back to the mirrored version.txt
|
||||
await using var provider = fileDownloader.DownloadFile(new Uri(mirroredVersionTxt), null);
|
||||
edgeVersion = await GetVersionFromResponse(mirroredVersionTxt);
|
||||
}
|
||||
|
||||
return edgeVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
<TargetFramework>$(TgsFrameworkVersion)</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Tgstation.Server.Client.GraphQL\Tgstation.Server.Client.GraphQL.csproj" />
|
||||
<ProjectReference Include="..\..\src\Tgstation.Server.Host.Watchdog\Tgstation.Server.Host.Watchdog.csproj" />
|
||||
|
||||
Reference in New Issue
Block a user