mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-28 23:52:36 +01:00
Merge branch 'dev' into 1458-AddMapThreadsParam
This commit is contained in:
@@ -8,6 +8,7 @@ coverage:
|
||||
comment:
|
||||
layout: "header, diff, changes"
|
||||
ignore:
|
||||
- "build"
|
||||
- "src/Tgstation.Server.Host/Database/Design"
|
||||
- "src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs"
|
||||
- "src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs"
|
||||
|
||||
@@ -23,6 +23,7 @@ build/**
|
||||
!build/ControlPanelVersion.props
|
||||
!build/Common.props
|
||||
!build/NugetCommon.props
|
||||
!build/uac_elevation_manifest.xml
|
||||
docs
|
||||
src/DMAPI
|
||||
src/Tgstation.Server.Host/ClientApp
|
||||
|
||||
@@ -12,6 +12,14 @@ indent_size = 4
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.wxs]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.wixproj]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.dm]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
+11
-2
@@ -34,9 +34,13 @@ You can of course, as always, ask for help at [#coderbus](irc://irc.rizon.net/co
|
||||
|
||||
### Development Environment
|
||||
|
||||
You need the Dotnet 6.0 SDK and npm>=v5.7 (in your PATH) to compile the server. In order to build the service version you also need a .NET 4.7.1 build chain
|
||||
You need the .NET 6.0 SDK and npm>=v5.7 (in your PATH) to compile the server.
|
||||
|
||||
The recommended IDE is Visual Studio 2019 which has installation options for both of these.
|
||||
The recommended IDE is Visual Studio 2022 or VSCode.
|
||||
|
||||
In order to build the service version and/or the Windows installer you need a to run on Windows.
|
||||
|
||||
In addition, the installer project uses the Wix v4 Toolset which will cause an error on loading the .sln in Visual Studio if the [HeatWave for VS2022 Extension](https://marketplace.visualstudio.com/items?itemName=FireGiant.FireGiantHeatWaveDev17) is not installed.
|
||||
|
||||
In order to run the integration tests you must have the following environment variables set. To run them more accurately, include the optional ones.
|
||||
- `TGS_TEST_DATABASE_TYPE`: `MySql`, `MariaDB`, `PostgresSql`, or `SqlServer`.
|
||||
@@ -70,6 +74,8 @@ If you don't plan on deploying TGS, the following secrets can be omitted:
|
||||
- Secret `DOCKER_USERNAME`: Login username for Docker image push.
|
||||
- Secret `DOCKER_PASSWORD`: Login password for Docker image push.
|
||||
- Secret `NUGET_API_KEY`: Nuget.org API Key for client libraries push.
|
||||
- Secret `CODE_SIGNING_BASE64`: Base64 string of a .pfx file containing a Windows code-signing certificate.
|
||||
- Secret `CODE_SIGNING_PASSWORD`: Password for importing the above .pfx.
|
||||
|
||||
### Know your Code
|
||||
|
||||
@@ -146,10 +152,13 @@ void Hello()
|
||||
{
|
||||
if (!thing1)
|
||||
return;
|
||||
|
||||
if (thing2)
|
||||
return;
|
||||
|
||||
if (thing3 != 30)
|
||||
return;
|
||||
|
||||
do stuff
|
||||
}
|
||||
```
|
||||
|
||||
@@ -16,7 +16,7 @@ concurrency:
|
||||
jobs:
|
||||
approve-pr-if-dominion-is-author:
|
||||
name: Approve PR if Dominion is Author
|
||||
if: github.event.pull_request.user.login == 'Cyberboss' && !github.event.pull_request.draft && (github.event.pull_request.base.repo.owner.login == 'tgstation' || github.event.pull_request.base.repo.owner.login == 'Cyberboss')
|
||||
if: github.event.pull_request.user.login == 'Cyberboss' && !github.event.pull_request.draft && !contains(github.event.pull_request.body, '- [ ]') && (github.event.pull_request.base.repo.owner.login == 'tgstation' || github.event.pull_request.base.repo.owner.login == 'Cyberboss')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: GitHub API Call
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
name: 'CI'
|
||||
# THE MAIN BIG CHUNGUS
|
||||
# Does CI on push/PR/cron. Deployments on push when triggered
|
||||
# - Validates Documentation
|
||||
# - Builds C# and DMAPI
|
||||
# - Tests everything on massive matrix
|
||||
# - Packages
|
||||
# - Tests package installs/services/uninstalls
|
||||
# - Properly versions everything based on build/Version.props
|
||||
# - Checks commit tags for deployment intents
|
||||
# - Deploys DreamMaker API zip [DMDeploy] (dev/master)
|
||||
# - Deploys Nuget Packages [NugetDeploy] (dev/master)
|
||||
# - Deploys HTTP API swagger.json [APIDeploy] (dev/master)
|
||||
# - Deploys tgstation-server [TGSDeploy] (master)
|
||||
# - GitHub Releases: https://github.com/tgstation/tgstation-server/releases
|
||||
# - Docker: https://hub.docker.com/r/tgstation/server
|
||||
# - apt repo: https://github.com/tgstation/tgstation-ppa
|
||||
# - winget: https://github.com/microsoft/winget-pkgs/tree/master/manifests/t/Tgstation/Server
|
||||
name: 'CI Pipeline'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -21,6 +38,9 @@ env:
|
||||
TGS_DOTNET_VERSION: 6.0.x
|
||||
TGS_TEST_GITHUB_TOKEN: ${{ secrets.LIVE_TESTS_TOKEN }}
|
||||
TGS_RELEASE_NOTES_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
WINGET_PUSH_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
PACKAGING_KEYGRIP: ${{ vars.PACKAGING_KEYGRIP }}
|
||||
PACKAGING_PRIVATE_KEY_PASSPHRASE: ${{ secrets.PACKAGING_PRIVATE_KEY_PASSPHRASE }}
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
concurrency:
|
||||
@@ -50,7 +70,7 @@ jobs:
|
||||
run: exit 1
|
||||
|
||||
start-ci-run-gate:
|
||||
name: Start CI Run Gate
|
||||
name: CI Start Gate
|
||||
needs: security-checkpoint
|
||||
runs-on: ubuntu-latest
|
||||
if: "!(cancelled() || failure()) && (needs.security-checkpoint.result == 'success' || (needs.security-checkpoint.result == 'skipped' && (github.event_name == 'push' || github.event_name == 'schedule' || (github.event.pull_request.head.repo.id == github.event.pull_request.base.repo.id && github.event_name != 'pull_request_target'))))"
|
||||
@@ -230,11 +250,14 @@ jobs:
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c ${{ matrix.configuration }}NoService
|
||||
run: dotnet build -c ${{ matrix.configuration }}NoWindows
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: sudo dotnet test --no-build --logger GitHubActions --filter FullyQualifiedName!~TestLiveServer -c ${{ matrix.configuration }}NoService --collect:"XPlat Code Coverage" --settings build/coverlet.runsettings --results-directory ./TestResults tgstation-server.sln
|
||||
run: sudo dotnet test --no-build --logger GitHubActions --filter FullyQualifiedName!~TestLiveServer -c ${{ matrix.configuration }}NoWindows --collect:"XPlat Code Coverage" --settings build/coverlet.runsettings --results-directory ./TestResults tgstation-server.sln
|
||||
|
||||
- name: Store Code Coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -270,11 +293,14 @@ jobs:
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c ${{ matrix.configuration }}
|
||||
run: dotnet build -c ${{ matrix.configuration }}NoWix
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: dotnet test --no-build --logger GitHubActions --filter FullyQualifiedName!~TestLiveServer -c ${{ matrix.configuration }} --collect:"XPlat Code Coverage" --settings build/coverlet.runsettings --results-directory ./TestResults tgstation-server.sln
|
||||
run: dotnet test --no-build --logger GitHubActions --filter FullyQualifiedName!~TestLiveServer -c ${{ matrix.configuration }}NoWix --collect:"XPlat Code Coverage" --settings build/coverlet.runsettings --results-directory ./TestResults tgstation-server.sln
|
||||
|
||||
- name: Store Code Coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -372,6 +398,9 @@ jobs:
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c ${{ matrix.configuration }} tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj
|
||||
|
||||
@@ -409,7 +438,6 @@ jobs:
|
||||
cd ../Tgstation.Server.Host
|
||||
dotnet publish -c ${{ matrix.configuration }} --no-build -o ../../Artifacts/Service/lib/Default
|
||||
mv ../../Artifacts/Service/lib/Default/appsettings.yml ../../Artifacts/Service/appsettings.yml
|
||||
rm ../../Artifacts/Service/lib/Default/Tgstation.Server.Host.exe
|
||||
|
||||
- name: Store Server Service
|
||||
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' }}
|
||||
@@ -418,6 +446,21 @@ jobs:
|
||||
name: ServerService
|
||||
path: Artifacts/Service/
|
||||
|
||||
- name: Install Code Signing Certificate
|
||||
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' }}
|
||||
shell: powershell
|
||||
run: |
|
||||
$pfxBytes = [convert]::FromBase64String("${{ secrets.CODE_SIGNING_BASE64 }}")
|
||||
[IO.File]::WriteAllBytes("tg_codesigning.pfx", $pfxBytes)
|
||||
$certPassword = ConvertTo-SecureString -String "${{ secrets.CODE_SIGNING_PASSWORD }}" -Force -AsPlainText
|
||||
Import-PfxCertificate -FilePath tg_codesigning.pfx -Cert Cert:\CurrentUser\My -Password $certPassword
|
||||
rm tg_codesigning.pfx
|
||||
|
||||
- name: Test Sign Service .exe
|
||||
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' }}
|
||||
shell: powershell
|
||||
run: Set-AuthenticodeSignature Artifacts/Service/Tgstation.Server.Host.Service.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq "${{ vars.CODE_SIGNING_THUMBPRINT }}" }) -TimestampServer "http://timestamp.digicert.com"
|
||||
|
||||
linux-integration-tests:
|
||||
name: Linux Live Tests
|
||||
needs: dmapi-build
|
||||
@@ -524,8 +567,11 @@ jobs:
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c ${{ matrix.configuration }}NoService tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj
|
||||
run: dotnet build -c ${{ matrix.configuration }}NoWindows tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj
|
||||
|
||||
- name: Cache BYOND .zips
|
||||
uses: actions/cache@v3
|
||||
@@ -538,7 +584,7 @@ jobs:
|
||||
run: |
|
||||
cd tests/Tgstation.Server.Tests
|
||||
sleep 10
|
||||
dotnet test -c ${{ matrix.configuration }}NoService --filter FullyQualifiedName~TestLiveServer --logger GitHubActions --no-build --collect:"XPlat Code Coverage" --settings ../../build/coverlet.runsettings --results-directory ../../TestResults
|
||||
dotnet test -c ${{ matrix.configuration }}NoWindows --filter FullyQualifiedName~TestLiveServer --logger GitHubActions --no-build --collect:"XPlat Code Coverage" --settings ../../build/coverlet.runsettings --results-directory ../../TestResults
|
||||
|
||||
- name: Store Code Coverage
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -552,17 +598,15 @@ jobs:
|
||||
cd src/Tgstation.Server.Host.Console
|
||||
dotnet publish -c ${{ matrix.configuration }} -o ../../Artifacts/Console
|
||||
cd ../Tgstation.Server.Host
|
||||
dotnet publish -c ${{ matrix.configuration }}NoService --no-build -o ../../Artifacts/Console/lib/Default
|
||||
dotnet publish -c ${{ matrix.configuration }}NoWindows --no-build -o ../../Artifacts/Console/lib/Default
|
||||
mv ../../Artifacts/Console/lib/Default/appsettings.yml ../../Artifacts/Console/appsettings.yml
|
||||
rm ../../Artifacts/Console/lib/Default/Tgstation.Server.Host
|
||||
|
||||
- name: Package Server Update Package
|
||||
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'System' && matrix.database-type == 'PostgresSql' }}
|
||||
run: |
|
||||
cd src/Tgstation.Server.Host
|
||||
dotnet publish -c ${{ matrix.configuration }}NoService --no-build -o ../../Artifacts/ServerUpdate
|
||||
dotnet publish -c ${{ matrix.configuration }}NoWindows --no-build -o ../../Artifacts/ServerUpdate
|
||||
rm ../../Artifacts/ServerUpdate/appsettings.yml
|
||||
rm ../../Artifacts/ServerUpdate/Tgstation.Server.Host
|
||||
|
||||
- name: Store Server Console
|
||||
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'System' && matrix.database-type == 'MariaDB' }}
|
||||
@@ -863,30 +907,31 @@ jobs:
|
||||
fail_ci_if_error: true
|
||||
|
||||
build-deb:
|
||||
name: Build .deb Package
|
||||
name: Build .deb Package # Can't do i386 due to https://github.com/dotnet/core/issues/4595
|
||||
needs: start-ci-run-gate
|
||||
runs-on: ubuntu-latest
|
||||
if: "!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success'"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [ 'amd64' ] # Can't do i386 due to https://github.com/dotnet/core/issues/4595
|
||||
steps:
|
||||
- name: Import GPG Key
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gnupg2
|
||||
sudo apt-get install -y -o APT::Immediate-Configure=0 libstdc++6:i386 libgcc-s1:i386 gnupg2
|
||||
echo "${{ secrets.PACKAGING_PRIVATE_KEY }}" > private.pgp
|
||||
echo ${{ secrets.PACKAGING_PRIVATE_KEY_PASSPHRASE }} | gpg --batch --yes --passphrase-fd 0 --import private.pgp
|
||||
rm private.pgp
|
||||
|
||||
- name: Checkout
|
||||
- name: Checkout (Branch)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push' || github.event_name == 'schedule'
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push' && github.event_name != 'schedule'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Execute Build Script
|
||||
env:
|
||||
PACKAGING_KEYGRIP: ${{ vars.PACKAGING_KEYGRIP }}
|
||||
PACKAGING_PRIVATE_KEY_PASSPHRASE: ${{ secrets.PACKAGING_PRIVATE_KEY_PASSPHRASE }}
|
||||
run: sudo -E build/package/deb/build_package.sh
|
||||
|
||||
- name: Parse TGS version
|
||||
@@ -899,6 +944,32 @@ jobs:
|
||||
gpg --verify tgstation-server_${{ env.TGS_VERSION }}-1_amd64.changes
|
||||
gpg --verify tgstation-server_${{ env.TGS_VERSION }}-1_amd64.buildinfo
|
||||
|
||||
- name: Test Install
|
||||
run: |
|
||||
sudo mkdir /etc/tgstation-server
|
||||
sudo cp build/package/appsettings.GitHub.yml /etc/tgstation-server/appsettings.Production.yml
|
||||
sudo apt-get install -y ./tgstation-server_${{ env.TGS_VERSION }}-1_amd64.deb
|
||||
sudo ls -al /etc/tgstation-server
|
||||
sudo cat /etc/tgstation-server/appsettings.Production.yml
|
||||
sudo cat /etc/tgstation-server/appsettings.yml
|
||||
ls -al /opt/tgstation-server
|
||||
cat /opt/tgstation-server/lib/Default/Tgstation.Server.Host.deps.json
|
||||
cat /usr/bin/tgs-configure
|
||||
|
||||
- name: Test Service
|
||||
run: |
|
||||
systemctl status tgstation-server
|
||||
|
||||
- name: Test Uninstall # Wait 10s for service to initialize
|
||||
shell: bash
|
||||
run: |
|
||||
sleep 10
|
||||
sudo apt-get remove -y tgstation-server
|
||||
if [[ -d "/opt/tgstation-server" ]]; then
|
||||
ls -al /opt/tgstation-server
|
||||
exit 2
|
||||
fi
|
||||
|
||||
- name: Create Packaging Archive
|
||||
run: tar cfJ tgstation-server-v${{ env.TGS_VERSION }}.debian.packaging.tar.xz tgstation-server_*
|
||||
|
||||
@@ -908,11 +979,186 @@ jobs:
|
||||
name: packaging-debian
|
||||
path: tgstation-server-v${{ env.TGS_VERSION }}.debian.packaging.tar.xz
|
||||
|
||||
deployment-gate:
|
||||
name: Deployment Gate
|
||||
needs: [ dox-build, docker-build, build-deb, validate-openapi-spec, upload-code-coverage ]
|
||||
build-msi:
|
||||
name: Build Windows Installer .exe
|
||||
needs: start-ci-run-gate
|
||||
runs-on: windows-latest
|
||||
if: "!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success'"
|
||||
steps:
|
||||
- name: Install winget
|
||||
uses: Cyberboss/install-winget@v1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ env.WINGET_PUSH_TOKEN }}
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
|
||||
|
||||
- name: Checkout (Branch)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push' || github.event_name == 'schedule'
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push' && github.event_name != 'schedule'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Restore Wix dotnet Tool
|
||||
run: |
|
||||
cd build/package/winget
|
||||
dotnet tool restore
|
||||
|
||||
- name: Validate winget Manifest
|
||||
run: winget validate --manifest build/package/winget/manifest
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build Host
|
||||
run: dotnet build -c Release src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
|
||||
|
||||
- name: Build Service
|
||||
run: dotnet build -c Release src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj
|
||||
|
||||
- name: Prepare Artifacts
|
||||
shell: powershell
|
||||
run: build/package/winget/prepare_installer_input_artifacts.ps1
|
||||
|
||||
- name: Build Installer .exe
|
||||
run: |
|
||||
cd build/package/winget/Tgstation.Server.Host.Service.Wix.Bundle
|
||||
dotnet build -c Release
|
||||
|
||||
- name: Install Code Signing Certificate
|
||||
shell: powershell
|
||||
run: |
|
||||
$pfxBytes = [convert]::FromBase64String("${{ secrets.CODE_SIGNING_BASE64 }}")
|
||||
[IO.File]::WriteAllBytes("tg_codesigning.pfx", $pfxBytes)
|
||||
$certPassword = ConvertTo-SecureString -String "${{ secrets.CODE_SIGNING_PASSWORD }}" -Force -AsPlainText
|
||||
Import-PfxCertificate -FilePath tg_codesigning.pfx -Cert Cert:\CurrentUser\My -Password $certPassword
|
||||
rm tg_codesigning.pfx
|
||||
|
||||
- name: Sign Installer .exe for Testing # https://wixtoolset.org/docs/tools/signing/
|
||||
shell: powershell
|
||||
run: |
|
||||
cd build/package/winget
|
||||
dotnet wix burn detach Tgstation.Server.Host.Service.Wix.Bundle/bin/Release/tgstation-server-installer.exe -engine burnengine.exe
|
||||
Set-AuthenticodeSignature burnengine.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq "${{ vars.CODE_SIGNING_THUMBPRINT }}" }) -TimestampServer "http://timestamp.digicert.com"
|
||||
dotnet wix burn reattach Tgstation.Server.Host.Service.Wix.Bundle/bin/Release/tgstation-server-installer.exe -engine burnengine.exe -o test-installer.exe
|
||||
Set-AuthenticodeSignature test-installer.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq "${{ vars.CODE_SIGNING_THUMBPRINT }}" }) -TimestampServer "http://timestamp.digicert.com"
|
||||
|
||||
- name: Test Install # Sanity checks the .deps.json exists, which the installation is broken without
|
||||
shell: powershell # If it's missing, I found that <PrivateAssets> in <PackageReference> elements were the problem
|
||||
run: |
|
||||
mkdir C:/ProgramData/tgstation-server
|
||||
cp build/package/appsettings.GitHub.yml C:/ProgramData/tgstation-server/appsettings.Production.yml
|
||||
$file = [System.IO.Path]::GetFullPath("build/package/winget/test-installer.exe")
|
||||
$log = [System.IO.Path]::GetFullPath("install.log")
|
||||
$procMain = Start-Process -FilePath $file "/install /silent /log `"$log`"" -NoNewWindow -PassThru -Wait
|
||||
if (Test-Path -Path $log -PathType Leaf) {
|
||||
Get-Content $log
|
||||
}
|
||||
$installCode = $procMain.ExitCode
|
||||
if($installCode -ne 0) {
|
||||
Write-Host "ERROR INSTALLER EXIT CODE $installCode"
|
||||
exit 3
|
||||
}
|
||||
if (-Not (Test-Path -Path "C:/Program Files (x86)/tgstation-server/lib/Default/Tgstation.Server.Host.deps.json" -PathType Leaf)) {
|
||||
exit 2
|
||||
}
|
||||
if (-Not (Test-Path -Path "C:/ProgramData/tgstation-server/appsettings.yml" -PathType Leaf)) {
|
||||
exit 4
|
||||
}
|
||||
|
||||
- name: Test Service
|
||||
shell: powershell
|
||||
run: |
|
||||
$service=Get-Service -Name tgstation-server -ErrorAction SilentlyContinue
|
||||
if ($service.Length -eq 0) {
|
||||
exit 3
|
||||
}
|
||||
if ($service[0].Status -ne "Running") {
|
||||
exit 4
|
||||
}
|
||||
|
||||
- name: Test Uninstall # Sanity checks the .deps.json exists, which the installation is broken without
|
||||
shell: powershell
|
||||
run: |
|
||||
$file = [System.IO.Path]::GetFullPath("build/package/winget/test-installer.exe")
|
||||
$log = [System.IO.Path]::GetFullPath("uninstall.log")
|
||||
$procMain = Start-Process -FilePath $file "/uninstall /silent /log `"$log`"" -NoNewWindow -PassThru -Wait
|
||||
if (Test-Path -Path $log -PathType Leaf) {
|
||||
Get-Content $log
|
||||
}
|
||||
$installCode = $procMain.ExitCode
|
||||
if($installCode -ne 0) {
|
||||
Write-Host "ERROR INSTALLER EXIT CODE $installCode"
|
||||
exit 3
|
||||
}
|
||||
$service=Get-Service -Name tgstation-server -ErrorAction SilentlyContinue
|
||||
if ($service.Length -gt 0) {
|
||||
echo $service
|
||||
exit 2
|
||||
}
|
||||
|
||||
- name: Upload Unsigned Installer .exe
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: packaging-preview-windows
|
||||
path: build/package/winget/Tgstation.Server.Host.Service.Wix.Bundle/bin/Release/tgstation-server-installer.exe
|
||||
|
||||
check-winget-pr-template:
|
||||
name: Check winget-pkgs Pull Request Template is up to date
|
||||
needs: start-ci-run-gate
|
||||
if: "!(cancelled() || failure()) && needs.start-ci-run-gate.result == 'success'"
|
||||
runs-on: ubuntu-latest
|
||||
if: "!(cancelled() || failure()) && needs.dox-build.result == 'success' && needs.docker-build.result == 'success' && needs.build-deb.result == 'success' && needs.validate-openapi-spec.result == 'success' && needs.upload-code-coverage.result == 'success' && github.event_name == 'push'"
|
||||
steps:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
|
||||
|
||||
- name: Retrieve Latest winget-pkgs PULL_REQUEST_TEMPLATE commit SHA from GitHub API
|
||||
id: get-sha
|
||||
run: |
|
||||
curl -L -u "${{ vars.DEV_PUSH_USERNAME }}:${{ secrets.DEV_PUSH_TOKEN }}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" -o commits.json https://api.github.com/repos/microsoft/winget-pkgs/commits?path=.github/PULL_REQUEST_TEMPLATE.md
|
||||
echo "pr_template_sha=$(cat commits.json | jq '.[0].sha')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Checkout (Branch)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name == 'push' || github.event_name == 'schedule'
|
||||
|
||||
- name: Checkout (PR Merge)
|
||||
uses: actions/checkout@v3
|
||||
if: github.event_name != 'push' && github.event_name != 'schedule'
|
||||
with:
|
||||
ref: "refs/pull/${{ github.event.number }}/merge"
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build ReleaseNotes
|
||||
run: dotnet build -c Release tools/ReleaseNotes/ReleaseNotes.csproj
|
||||
|
||||
- name: Run ReleaseNotes Check
|
||||
run: dotnet run -c Release --no-build --project tools/ReleaseNotes --winget-template-check ${{ steps.get-sha.outputs.pr_template_sha }}
|
||||
|
||||
ci-completion-gate: # This job exists so there isn't a moving target for branch protections
|
||||
name: CI Completion Gate
|
||||
needs: [ dox-build, docker-build, build-deb, build-msi, validate-openapi-spec, upload-code-coverage, check-winget-pr-template ]
|
||||
runs-on: ubuntu-latest
|
||||
if: "!(cancelled() || failure()) && needs.dox-build.result == 'success' && needs.docker-build.result == 'success' && needs.build-deb.result == 'success' && needs.build-msi.result == 'success' && needs.validate-openapi-spec.result == 'success' && needs.upload-code-coverage.result == 'success' && needs.check-winget-pr-template.result == 'success'"
|
||||
steps:
|
||||
- name: GitHub Requires at Least One Step for a Job
|
||||
run: exit 0
|
||||
|
||||
deployment-gate:
|
||||
name: Deployment Start Gate
|
||||
needs: ci-completion-gate
|
||||
runs-on: ubuntu-latest
|
||||
if: "!(cancelled() || failure()) && needs.ci-completion-gate.result == 'success' && github.event_name == 'push'"
|
||||
steps:
|
||||
- name: GitHub Requires at Least One Step for a Job
|
||||
run: exit 0
|
||||
@@ -1054,8 +1300,14 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build ReleaseNotes
|
||||
run: dotnet build -c Release tools/ReleaseNotes/ReleaseNotes.csproj
|
||||
|
||||
- name: Run ReleaseNotes with --ensure-release
|
||||
run: dotnet run -c Release --project tools/ReleaseNotes --ensure-release
|
||||
run: dotnet run -c Release --no-build --project tools/ReleaseNotes --ensure-release
|
||||
|
||||
deploy-tgs:
|
||||
name: Deploy TGS
|
||||
@@ -1071,6 +1323,36 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Restore
|
||||
run: dotnet restore
|
||||
|
||||
- name: Restore Wix dotnet Tool
|
||||
run: |
|
||||
cd build/package/winget
|
||||
dotnet tool restore
|
||||
|
||||
- name: Build ReleaseNotes
|
||||
run: dotnet build -c Release tools/ReleaseNotes/ReleaseNotes.csproj
|
||||
|
||||
# We need to rebuild the installer.exe so it can be properly signed
|
||||
- name: Restore
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build Host
|
||||
run: dotnet build -c Release src/Tgstation.Server.Host/Tgstation.Server.Host.csproj
|
||||
|
||||
- name: Build Service
|
||||
run: dotnet build -c Release src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj
|
||||
|
||||
- name: Prepare Artifacts
|
||||
shell: powershell
|
||||
run: build/package/winget/prepare_installer_input_artifacts.ps1
|
||||
|
||||
- name: Build Installer .exe
|
||||
run: |
|
||||
cd build/package/winget/Tgstation.Server.Host.Service.Wix.Bundle
|
||||
dotnet build -c Release
|
||||
|
||||
- name: Parse TGS version
|
||||
shell: powershell
|
||||
run: |
|
||||
@@ -1108,6 +1390,28 @@ jobs:
|
||||
name: packaging-debian
|
||||
path: packaging-debian
|
||||
|
||||
- name: Install Code Signing Certificate
|
||||
shell: powershell
|
||||
run: |
|
||||
$pfxBytes = [convert]::FromBase64String("${{ secrets.CODE_SIGNING_BASE64 }}")
|
||||
[IO.File]::WriteAllBytes("tg_codesigning.pfx", $pfxBytes)
|
||||
$certPassword = ConvertTo-SecureString -String "${{ secrets.CODE_SIGNING_PASSWORD }}" -Force -AsPlainText
|
||||
Import-PfxCertificate -FilePath tg_codesigning.pfx -Cert Cert:\CurrentUser\My -Password $certPassword
|
||||
rm tg_codesigning.pfx
|
||||
|
||||
- name: Sign Installer .exe # https://wixtoolset.org/docs/tools/signing/
|
||||
shell: powershell
|
||||
run: |
|
||||
cd build/package/winget
|
||||
dotnet wix burn detach Tgstation.Server.Host.Service.Wix.Bundle/bin/Release/tgstation-server-installer.exe -engine burnengine.exe
|
||||
Set-AuthenticodeSignature burnengine.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq "${{ vars.CODE_SIGNING_THUMBPRINT }}" }) -TimestampServer "http://timestamp.digicert.com"
|
||||
dotnet wix burn reattach Tgstation.Server.Host.Service.Wix.Bundle/bin/Release/tgstation-server-installer.exe -engine burnengine.exe -o tgstation-server-installer.exe
|
||||
Set-AuthenticodeSignature tgstation-server-installer.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq "${{ vars.CODE_SIGNING_THUMBPRINT }}" }) -TimestampServer "http://timestamp.digicert.com"
|
||||
|
||||
- name: Sign Service .exe
|
||||
shell: powershell
|
||||
run: Set-AuthenticodeSignature ServerService/Tgstation.Server.Host.Service.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq "${{ vars.CODE_SIGNING_THUMBPRINT }}" }) -TimestampServer "http://timestamp.digicert.com"
|
||||
|
||||
- name: Zip Artifacts
|
||||
shell: powershell
|
||||
run: |
|
||||
@@ -1118,7 +1422,7 @@ jobs:
|
||||
&"C:/Program Files/7-Zip/7z.exe" a ServerUpdatePackage.zip ./ServerUpdatePackage/* -tzip
|
||||
|
||||
- name: Generate Release Notes
|
||||
run: dotnet run -c Release --project tools/ReleaseNotes ${{ env.TGS_VERSION }}
|
||||
run: dotnet run -c Release --no-build --project tools/ReleaseNotes ${{ env.TGS_VERSION }}
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: actions/create-release@v1
|
||||
@@ -1191,6 +1495,16 @@ jobs:
|
||||
asset_name: tgstation-server-v${{ env.TGS_VERSION }}.debian.packaging.tar.xz
|
||||
asset_content_type: application/x-tar
|
||||
|
||||
- name: Upload Installer .exe
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.DEV_PUSH_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./build/package/winget/tgstation-server-installer.exe
|
||||
asset_name: tgstation-server-installer.exe
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
deploy-docker:
|
||||
name: Deploy TGS (Docker)
|
||||
needs: deploy-tgs
|
||||
@@ -1232,4 +1546,39 @@ jobs:
|
||||
|
||||
- name: Trigger tgstation-ppa workflow
|
||||
run: |
|
||||
curl -XPOST -u "${{ vars.DEV_PUSH_USERNAME }}:${{ secrets.DEV_PUSH_TOKEN }}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/tgstation/tgstation-ppa/actions/workflows/add_tgs_version.yml/dispatches --data '{"ref":"main","inputs":{"tgs_semver": "${{ env.TGS_VERSION }}"}}'
|
||||
curl -XPOST -u "${{ vars.DEV_PUSH_USERNAME }}:${{ secrets.DEV_PUSH_TOKEN }}" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/tgstation/tgstation-ppa/actions/workflows/add_tgs_version.yml/dispatches --data '{"ref":"main","inputs":{"tgs_semver": "${{ env.TGS_VERSION }}"}
|
||||
|
||||
deploy-winget:
|
||||
name: Deploy TGS (winget)
|
||||
needs: deploy-tgs
|
||||
if: "!(cancelled() || failure()) && needs.deploy-tgs.result == 'success'"
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}
|
||||
|
||||
- name: Install winget
|
||||
uses: Cyberboss/install-winget@v1
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ env.WINGET_PUSH_TOKEN }}
|
||||
|
||||
- name: Install wingetcreate
|
||||
run: winget install wingetcreate --disable-interactivity --accept-source-agreements
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build ReleaseNotes
|
||||
run: dotnet build -c Release tools/ReleaseNotes
|
||||
|
||||
- name: Execute Push Script
|
||||
shell: powershell
|
||||
run: build/package/winget/push_manifest.ps1
|
||||
|
||||
- name: Run ReleaseNotes with --link-winget
|
||||
shell: powershell
|
||||
run: |
|
||||
Sleep 15
|
||||
dotnet run -c Release --no-build --project tools/ReleaseNotes --link-winget ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
languages: csharp
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c ReleaseNoService
|
||||
run: dotnet build -c ReleaseNoWindows
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
@@ -5,6 +5,7 @@ _ReSharper.*
|
||||
packages/
|
||||
artifacts/
|
||||
.vs/
|
||||
.vscode/
|
||||
*.user
|
||||
*.suo
|
||||
*.userprefs
|
||||
|
||||
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"gbasood.byond-dm-language-support",
|
||||
"ms-vscode.csharp",
|
||||
"k--kato.docomment",
|
||||
"peterjausovec.vscode-docker",
|
||||
"bbenoist.doxygen"
|
||||
]
|
||||
}
|
||||
Vendored
-21
@@ -1,21 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Server",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/src/Tgstation.Server.Host/bin/Debug/net6.0/Tgstation.Server.Host.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/src/Tgstation.Server.Host",
|
||||
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
|
||||
"console": "externalTerminal",
|
||||
"stopAtEntry": false
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
-33
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"msbuild",
|
||||
"-p:Configuration=DebugNoService"
|
||||
],
|
||||
"problemMatcher": "$msCompile",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "test",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"test",
|
||||
"-p:Configuration=DebugNoService"
|
||||
],
|
||||
"problemMatcher": "$msCompile",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -18,40 +18,138 @@ Older server versions can be found in the V# branches of this repository. Note t
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- [ASP .NET Core Runtime (>= v6.0)](https://dotnet.microsoft.com/download/dotnet/6.0) (Choose the option to `Run Server Apps` for your system) If you plan to install tgstation-server as a Windows service, you should also ensure that your .NET Framework runtime version is >= v4.7.2 (Download can be found on same page). Ensure that the `dotnet` executable file is in your system's `PATH` variable (or that of the user's that will be running the server).
|
||||
- A [MariaDB](https://downloads.mariadb.org/), MySQL, [PostgresSQL](https://www.postgresql.org/download/), or [Microsoft SQL Server](https://www.microsoft.com/en-us/download/details.aspx?id=55994) database engine is required
|
||||
tgstation-server needs a relational database to store it's data.
|
||||
|
||||
If you're just a hobbyist server host, you can probably get away with using SQLite for this. SQLite is bundled with TGS and simply requires you to specify where on your machine you want to store the data.
|
||||
|
||||
_HOWEVER_
|
||||
|
||||
SQLite is not a battle-ready relational database. It doesn't scale well for any use case. TGS *strongly* recommends you use one of its supported standalone databases. Setting one of these up is more involved but worth the effort.
|
||||
|
||||
The supported standalone databases are:
|
||||
|
||||
- [MariaDB](https://downloads.mariadb.org/) _- NOTE: If you plan on hosting SpaceStation 13, this is the database most codebases support, making it an ideal choice_
|
||||
- [PostgresSQL](https://www.postgresql.org/download/)
|
||||
- [Microsoft SQL Server](https://www.microsoft.com/en-us/download/details.aspx?id=55994)
|
||||
- MySQL
|
||||
|
||||
TGS will require either:
|
||||
- No pre-existing database WITH schema creation permissions.
|
||||
or
|
||||
- Exclusive access to a database schema that TGS has full control over.
|
||||
|
||||
### Installation
|
||||
|
||||
1. [Download the latest release .zip](https://github.com/tgstation/tgstation-server/releases/latest). The `ServerService` package will only work on Windows. Choose `ServerConsole` if that is not your target OS or you prefer not to use the Windows service.
|
||||
2. Extract the .zip file to where you want the server to run from. Note the account running the server must have write and delete access to the `lib` subdirectory.
|
||||
Follow the instructions for your OS below.
|
||||
|
||||
#### Windows
|
||||
|
||||
If you wish to install the TGS as a service, run `Tgstation.Server.Host.Service.exe`. It should prompt you to install it. Click `Yes` and accept a potential UAC elevation prompt and the setup wizard should run.
|
||||
###### Note about Digital Signatures
|
||||
|
||||
Note that the Windows Service and installer executables requires administrative privileges. These are digitally signed against the Root CA managed by [Jordan Dominion](https://github.com/Cyberboss). Consider installing the certificate into your `Trusted Root Authorities` store for cleaner UAC prompts. The certificate can be downloaded [here](https://file.house/zpFb.cer), please validate the thumbprint is `70176acf7ffa2898fa5b5cd6e38b43b38ea5d07f` before installing.
|
||||
|
||||
##### winget (Windows 10 or later)
|
||||
|
||||
[winget](https://github.com/microsoft/winget-cli) installed is the easiest way to install the latest version of tgstation-server (provided Microsoft has approved the most recent package manifest).
|
||||
|
||||
Check if you have `winget` by running the following command.
|
||||
```
|
||||
winget --version
|
||||
```
|
||||
|
||||
If it returns an error that means you don't have winget. You can easily install it by running the following commands in an administrative Windows Powershell instance:
|
||||
```
|
||||
Import-Module Appx
|
||||
Invoke-WebRequest -Uri https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.7.3 -OutFile .\microsoft.ui.xaml.2.7.3.zip
|
||||
Expand-Archive .\microsoft.ui.xaml.2.7.3.zip
|
||||
Add-AppxPackage .\microsoft.ui.xaml.2.7.3\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx
|
||||
Add-AppxPackage -Path "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"
|
||||
Add-AppxPackage -Path "https://github.com/microsoft/winget-cli/releases/latest/download/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
|
||||
Remove-Item .\microsoft.ui.xaml.2.7.3\ -r
|
||||
Remove-Item .\microsoft.ui.xaml.2.7.3.zip
|
||||
```
|
||||
|
||||
Once winget is installed, simply run the following commands, accepting any prompts that may appear:
|
||||
|
||||
```ps
|
||||
winget install tgstation-server
|
||||
```
|
||||
|
||||
The required dotnet runtime may be installed as a pre-requisite.
|
||||
|
||||
Note: If you use the `-h` or `--disable-interactivity` winget arguments, you will need to either pre-configure TGS or configure and start the `tgstation-server` service after installing. A shortcut will be placed on your desktop and in your start menu to assist with this.
|
||||
|
||||
##### Installer
|
||||
|
||||
[Download the latest release's tgstation-server-installer.exe](https://github.com/tgstation/tgstation-server/releases/latest). Executing it will take you through the process of installing and configuring your server. The required dotnet runtime may be installed as a pre-requisite.
|
||||
|
||||
Note: If you use the `/silent` or `/passive` arguments to the installer, you will need to either pre-configure TGS or configure and start the `tgstation-server` service after installing. A shortcut will be placed on your desktop and in your start menu to assist with this.
|
||||
|
||||
##### Manual
|
||||
|
||||
If you don't have it installed already, download and install the [ASP .NET Core Runtime Hosting Bundle (>= v6.0)](https://dotnet.microsoft.com/download/dotnet/6.0). Ensure that the `dotnet` executable file is in your system's `PATH` variable (or that of the user's that will be running the server). You can test this by opening a command prompt and running `dotnet --list-runtimes`.
|
||||
|
||||
[Download the latest release .zip](https://github.com/tgstation/tgstation-server/releases/latest). Typically, you want the `ServerService.zip` package in order to run TGS as a Windows service. Choose `ServerConsole.zip` if you prefer to use a command line daemon.
|
||||
|
||||
Extract the .zip file to where you want the server to run from. Note the account running the server must have write, execute, and delete access to the `lib` subdirectory.
|
||||
|
||||
If you wish to install the TGS as a service, run `Tgstation.Server.Host.Service.exe`. It should prompt you to install it. Click `Yes` and the setup wizard should run.
|
||||
|
||||
Should you want a clean start, be sure to first uninstall the service by running `Tgstation.Server.Host.Service.exe -u` from the command line.
|
||||
|
||||
If using the console version, run ./tgs.bat in the root of the installation directory. Ctrl+C will close the server, terminating all live game instances.
|
||||
If using the console version, run `./tgs.bat` in the root of the installation directory. Ctrl+C will close the server, terminating all live game instances.
|
||||
|
||||
|
||||
#### Linux (Native)
|
||||
#### Linux
|
||||
|
||||
Installing natively is the recommended way to run tgstation-server on Linux.
|
||||
|
||||
##### Ubuntu
|
||||
|
||||
Install TGS and all it's dependencies via our apt repository, interactively configure it, and start the service with this one-liner:
|
||||
|
||||
```sh
|
||||
sudo dpkg --add-architecture i386 \
|
||||
&& sudo apt update \
|
||||
&& sudo apt install -y software-properties-common \
|
||||
&& sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv B6FD15EE7ED77676EAEAF910EEEDC8280A307527 \
|
||||
&& sudo add-apt-repository -y "deb https://tgstation.github.io/tgstation-ppa/debian unstable main" \
|
||||
&& sudo apt update \
|
||||
&& sudo apt install -y tgstation-server \
|
||||
&& sudo tgs-configure \
|
||||
&& sudo systemctl start tgstation-server
|
||||
```
|
||||
|
||||
##### Debian
|
||||
|
||||
The `aspnetcore-runtime-6.0` package isn't yet available on mainline Debian and must be [installed from Microsoft](https://learn.microsoft.com/en-us/dotnet/core/install/linux-debian) first. Use the following one-liner to add their packages repository.
|
||||
|
||||
```sh
|
||||
curl -L https://packages.microsoft.com/config/debian/11/packages-microsoft-prod.deb -o packages-microsoft-prod.deb \
|
||||
&& sudo dpkg -i packages-microsoft-prod.deb \
|
||||
&& rm packages-microsoft-prod.deb
|
||||
```
|
||||
|
||||
After that, run the same command as the Ubuntu installation.
|
||||
|
||||
_Support for more distros coming soon_
|
||||
|
||||
##### Manual
|
||||
|
||||
The following dependencies are required.
|
||||
|
||||
- aspnetcore-runtime-6.0 (Note, not all supported distros have this package, see the links above for official Microsoft installation instructions)
|
||||
- libc6-i386
|
||||
- libstdc++6:i386
|
||||
- gdb (for using gcore to create core dumps)
|
||||
- gcc-multilib (Only on 64-bit systems)
|
||||
- gdb (for using gcore to create core dumps)
|
||||
|
||||
If you have SystemD, we recommend installing the service unit [here](./build/tgstation-server.service). It assumes TGS is installed into `/opt/tgstation-server` but feel free to adjust it to your needs. Note that the server will need to have it's configuration file setup before running with SystemD.
|
||||
[Download the latest release .zip](https://github.com/tgstation/tgstation-server/releases/latest). Choose `ServerConsole`.
|
||||
|
||||
If you have SystemD installed, we recommend installing the service unit [here](./build/tgstation-server.service). It assumes TGS is installed into `/opt/tgstation-server` and you will be using the but feel free to adjust it to your needs. Note that the server will need to have it's configuration file setup before running with SystemD.
|
||||
|
||||
Alternatively, to launch the server in the current shell, run `./tgs.sh` in the root of the installation directory. The process will run in a blocking fashion. SIGQUIT will close the server, terminating all live game instances.
|
||||
|
||||
#### Docker (Linux)
|
||||
##### Docker
|
||||
|
||||
tgstation-server supports running in a docker container. The official image repository is located at https://hub.docker.com/r/tgstation/server. It can also be built locally by running `docker build . -f build/Dockerfile -t <your tag name>` in the repository root.
|
||||
|
||||
@@ -213,11 +311,13 @@ Note that the live detach for DreamDaemon servers is only supported for updates
|
||||
|
||||
For the Windows service version stop the `tgstation-server` service
|
||||
|
||||
For the SystemD managed service, use `systemctl stop tgstation-server`. DO NOT USE `systemctl kill` as this can create orphaned processes while leaving TGS running.
|
||||
|
||||
For the console version press `Ctrl+C` or send a SIGQUIT to the ORIGINAL dotnet process
|
||||
|
||||
For the docker version run `docker stop <your container name>`
|
||||
|
||||
### Updating
|
||||
### Updating the Game
|
||||
|
||||
## Integrating
|
||||
|
||||
@@ -371,15 +471,27 @@ Instances can be either part of a swarm or not. Once in the database they cannot
|
||||
|
||||
tgstation-server is controlled via a RESTful HTTP json API. Documentation on this API can be found [here](https://tgstation.github.io/tgstation-server/api.html). This section serves to document the concepts of the server. The API is versioned separately from the release version. A specification for it can be found in the api-vX.X.X git releases/tags.
|
||||
|
||||
### Updating
|
||||
### Updating TGS
|
||||
|
||||
TGS can self update without stopping your DreamDaemon servers. Releases made to this repository are bound by a contract that allows changes of the runtime assemblies without stopping your servers. Database migrations are automatically applied as well. Because of this REVERTING TO LOWER VERSIONS IS NOT OFFICIALLY SUPPORTED, do so at your own risk (check changes made to `/src/Tgstation.Server.Host/Models/Migrations`).
|
||||
TGS can self update without stopping your DreamDaemon servers. Releases made to this repository are bound by a contract that allows changes of the runtime assemblies without stopping your servers. Database migrations are automatically applied as well. Reverting to lower versions works but only works so far back in time, do so at your own risk (check changes made to `/src/Tgstation.Server.Host/Models/Migrations`).
|
||||
|
||||
Major version updates may require additional action on the part of the user (apart from the configuration changes).
|
||||
|
||||
#### Linux Notes
|
||||
|
||||
If TGS was installed via a package manager, using the TGS self updater will cause the version to change without notifying said package manager. This is not necessarily a problem if you're okay with the deviation.
|
||||
|
||||
To avoid this, use the package manager to update TGS. It is just as seamless as the self-updater.
|
||||
|
||||
##### apt
|
||||
|
||||
```sh
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
```
|
||||
|
||||
#### Notifications
|
||||
|
||||
If a server update is available, it will be indicated in the response from the GET /Administration endpoint. For more active notifications, you can subscribe to [this GitHub discussion](https://github.com/tgstation/tgstation-server/discussions/1322).
|
||||
If a server update is available, it will be indicated in the response from the GET /Administration endpoint and shown as a green exclamation mark in the webpanel navbar. For more active notifications, you can subscribe to [this GitHub discussion](https://github.com/tgstation/tgstation-server/discussions/1322).
|
||||
|
||||
### Users
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<Import Project="Version.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TgsNetVersion>net6.0</TgsNetVersion>
|
||||
<TgsFrameworkVersion>net$(TgsNetMajorVersion).0</TgsFrameworkVersion>
|
||||
<TgsNugetNetVersion>netstandard2.0</TgsNugetNetVersion>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<DebugType>Full</DebugType>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<Company>/tg/station 13</Company>
|
||||
<PackageProjectUrl>https://tgstation.github.io/tgstation-server</PackageProjectUrl>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageIcon>tgs.png</PackageIcon>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/tgstation/tgstation-server</RepositoryUrl>
|
||||
@@ -17,6 +18,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="" />
|
||||
<None Include="../../LICENSE" Pack="true" PackagePath="" />
|
||||
<None Include="../../build/tgs.png" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
|
||||
+5
-1
@@ -13,6 +13,10 @@
|
||||
<TgsInteropVersion>5.6.1</TgsInteropVersion>
|
||||
<TgsHostWatchdogVersion>1.3.0</TgsHostWatchdogVersion>
|
||||
<TgsContainerScriptVersion>1.2.1</TgsContainerScriptVersion>
|
||||
<TgsMigratorVersion>1.0.1</TgsMigratorVersion>
|
||||
<TgsMigratorVersion>1.0.2</TgsMigratorVersion>
|
||||
<TgsNetMajorVersion>6</TgsNetMajorVersion>
|
||||
<!-- Update this frequently with dotnet runtime patches. MAJOR MUST MATCH ABOVE! -->
|
||||
<!-- You also need to update the .Bundle project's ExePackagePayload hash and file size after changing it -->
|
||||
<TgsRedistUrl>https://dotnetcli.azureedge.net/dotnet/aspnetcore/Runtime/6.0.19/dotnet-hosting-6.0.19-win.exe</TgsRedistUrl>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Barebones config file used for testing service installs in GitHub actions
|
||||
Database:
|
||||
DatabaseType: Sqlite
|
||||
ConnectionString: Data Source=/tgs.sqlite3;Mode=ReadWriteCreate
|
||||
General:
|
||||
ConfigVersion: 4.6.0
|
||||
ApiPort: 5000
|
||||
GitHubAccessToken:
|
||||
SetupWizardMode: Never
|
||||
ByondTopicTimeout: 5000
|
||||
RestartTimeoutMinutes: 1
|
||||
ShutdownTimeoutMinutes: 300
|
||||
UseBasicWatchdog: false
|
||||
HostApiDocumentation: true
|
||||
SkipAddingByondFirewallException: false
|
||||
DeploymentDirectoryCopyTasksPerCore:
|
||||
MinimumPasswordLength: 15
|
||||
InstanceLimit: 10
|
||||
UserLimit: 100
|
||||
UserGroupLimit: 25
|
||||
ValidInstancePaths:
|
||||
FileLogging:
|
||||
Directory:
|
||||
Disable: false
|
||||
LogLevel: Trace
|
||||
MicrosoftLogLevel: Warning
|
||||
@@ -0,0 +1,2 @@
|
||||
# tgstation-server configuration file
|
||||
# See appsettings.yml or README.md for details on individual configuration options
|
||||
@@ -1,9 +1,7 @@
|
||||
#!/usr/bin/make -f
|
||||
|
||||
install:
|
||||
mkdir -p $(DESTDIR)/opt/tgstation-server
|
||||
cp -r artifacts/* $(DESTDIR)/opt/tgstation-server
|
||||
|
||||
uninstall:
|
||||
mkdir -p $(DESTDIR)/opt/tgstation-server
|
||||
cp -r artifacts/* $(DESTDIR)/opt/tgstation-server/
|
||||
build/package/deb/install_artifacts.sh "$(DESTDIR)"
|
||||
install -D build/package/appsettings.Initial.yml "$(DESTDIR)/etc/tgstation-server/appsettings.Production.yml"
|
||||
install src/Tgstation.Server.Host/appsettings.yml "$(DESTDIR)/etc/tgstation-server/appsettings.yml"
|
||||
install -D build/package/deb/tgs-configure "$(DESTDIR)/usr/bin/tgs-configure"
|
||||
|
||||
@@ -22,6 +22,7 @@ Depends:
|
||||
libstdc++6 [i386],
|
||||
gcc-multilib [amd64],
|
||||
Recommends:
|
||||
libsystemd0,
|
||||
gdb,
|
||||
Description: A production scale tool for BYOND server management
|
||||
This is a toolset to manage production BYOND servers. It includes the ability to update the server without having to stop or shutdown the server (the update will take effect on a "reboot" of the server), the ability to start the server and restart it if it crashes, as well as systems for managing code and game files, and locally merging GitHub Pull Requests for test deployments.
|
||||
|
||||
@@ -7,4 +7,4 @@ Files:
|
||||
*
|
||||
Copyright:
|
||||
2023 Jordan Dominion <Cyberboss@users.noreply.github.com>
|
||||
License: AGPL-3
|
||||
License: AGPL-3.0
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
pushd /opt/tgstation-server
|
||||
dotnet /opt/tgstation-server/lib/Default/Tgstation.Server.Host.dll General:SetupWizardMode=Only
|
||||
popd
|
||||
#!/bin/sh -e
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
if [ "$1" = "configure" ]; then
|
||||
chmod 600 /etc/tgstation-server
|
||||
deb-systemd-helper stop 'tgstation-server.service' >/dev/null || true
|
||||
|
||||
echo " _ _ _ _ "
|
||||
echo " | |_ __ _ ___| |_ __ _| |_(_) ___ _ __ ___ ___ _ ____ _____ _ __ "
|
||||
echo " | __/ _\` / __| __/ _\` | __| |/ _ \\| '_ \\ _____/ __|/ _ \\ '__\\ \\ / / _ \\ '__|"
|
||||
echo " | || (_| \\__ \\ || (_| | |_| | (_) | | | |_____\\__ \\ __/ | \\ V / __/ | "
|
||||
echo " \\__\\__, |___/\\__\\__,_|\\__|_|\\___/|_| |_| |___/\\___|_| \\_/ \\___|_| "
|
||||
echo " |___/ "
|
||||
echo "tgstation-server is now installed but must first be configured"
|
||||
echo "Run 'sudo tgs-configure' to interactively configure your server"
|
||||
echo "Alternatively, edit '/etc/tgstation-server/appsettings.Production.yml' to your desired specifications"
|
||||
echo "Once complete, run 'sudo systemctl start tgstation-server' to start the service"
|
||||
fi
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh -e
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
|
||||
@@ -7,14 +7,13 @@ export DH_VERBOSE = 1
|
||||
|
||||
override_dh_auto_clean:
|
||||
rm -rf artifacts
|
||||
dotnet clean -c ReleaseNoService
|
||||
dotnet clean -c ReleaseNoWindows
|
||||
|
||||
override_dh_auto_build:
|
||||
dotnet restore
|
||||
cd src/Tgstation.Server.Host.Console && dotnet publish -c Release -o ../../artifacts
|
||||
cd src/Tgstation.Server.Host && dotnet publish -c Release -o ../../artifacts/lib/Default
|
||||
mv artifacts/lib/Default/appsettings.yml artifacts/appsettings.yml
|
||||
rm artifacts/lib/Default/Tgstation.Server.Host
|
||||
rm artifacts/lib/Default/appsettings.yml
|
||||
|
||||
override_dh_auto_install:
|
||||
cp build/package/deb/MakeInstall ./Makefile
|
||||
@@ -25,4 +24,4 @@ override_dh_strip:
|
||||
override_dh_shlibdeps:
|
||||
|
||||
override_dh_installsystemd:
|
||||
dh_installsystemd -v --name=tgstation-server --restart-after-upgrade
|
||||
dh_installsystemd -v --restart-after-upgrade
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/sh -e
|
||||
|
||||
cd artifacts && for f in $(find * -type f); do install -D "$f" "$1/opt/tgstation-server/$f"; done
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd /opt/tgstation-server
|
||||
export General__SetupWizardMode=Only
|
||||
exec /usr/bin/dotnet /opt/tgstation-server/lib/Default/Tgstation.Server.Host.dll
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"wix": {
|
||||
"version": "4.0.1",
|
||||
"commands": [
|
||||
"wix"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:bal="http://wixtoolset.org/schemas/v4/wxs/bal" xmlns:netfx="http://wixtoolset.org/schemas/v4/wxs/netfx">
|
||||
<Bundle Name="tgstation-server" Manufacturer="/tg/station 13" Version="$(var.ProductVersion)" AboutUrl="https://github.com/tgstation/tgstation-server/blob/tgstation-server-v$(var.ProductVersion)/LICENSE" IconSourceFile="../../../tgs.ico" HelpUrl="https://github.com/tgstation/tgstation-server/discussions/categories/q-a" UpdateUrl="https://github.com/tgstation/tgstation-server/releases/latest" UpgradeCode="542535f4-49ad-45c4-9b96-6d8235ed8b87">
|
||||
<BootstrapperApplication>
|
||||
<bal:WixStandardBootstrapperApplication LicenseUrl="https://github.com/tgstation/tgstation/tree/tgstation-server-v$(var.ProductVersion)" Theme="hyperlinkLicense" LogoFile="../../../tgs.ico" ShowVersion="yes" SuppressOptionsUI="yes" />
|
||||
</BootstrapperApplication>
|
||||
|
||||
<netfx:DotNetCoreSearch RuntimeType="aspnet" Platform="x64" MajorVersion="$(var.NetMajorVersion)" Variable="AspNetCorex64Status" />
|
||||
<netfx:DotNetCoreSearch RuntimeType="aspnet" Platform="x86" MajorVersion="$(var.NetMajorVersion)" Variable="AspNetCorex86Status" />
|
||||
|
||||
<netfx:DotNetCoreSearch RuntimeType="core" Platform="x64" MajorVersion="$(var.NetMajorVersion)" Variable="NetCorex64Status" />
|
||||
<netfx:DotNetCoreSearch RuntimeType="core" Platform="x86" MajorVersion="$(var.NetMajorVersion)" Variable="NetCorex86Status" />
|
||||
|
||||
<WixVariable Id="AspNetCoreDetectCondition" Value="(NetCorex64Status AND AspNetCorex64Status) OR (NetCorex86Status AND AspNetCorex86Status)" />
|
||||
|
||||
<Chain>
|
||||
<ExePackage PerMachine="yes" DetectCondition="!(wix.AspNetCoreDetectCondition)" Vital="yes" Permanent="yes" Protocol="burn" InstallArguments="/install /quiet /norestart /log [HOSTING_BUNDLE_LOG_PATH]" RepairArguments="/repair /quiet /norestart /log [HOSTING_BUNDLE_LOG_PATH]" UninstallArguments="/uninstall /quiet /norestart /log [HOSTING_BUNDLE_LOG_PATH]" Cache="remove" LogPathVariable="HOSTING_BUNDLE_LOG_PATH" RepairCondition="FALSE">
|
||||
<ExePackagePayload Name="Microsoft.DotNet.HostingBundle.$(var.NetMajorVersion)" DownloadUrl="$(var.AspNetRedistUrl)" Hash="FADC48A76561A7ABD5CC599DB737AFC2B1638013C95E29CFC8955CACA1D57004BD9078C6EF9696BECFD75BAA2983FDC2FF4382C9EBDF833A11316C2DEC218884" Size="71391856" Compressed="no" />
|
||||
</ExePackage>
|
||||
<MsiPackage SourceFile="$(var.Tgstation.Server.Host.Service.Wix.TargetPath)">
|
||||
<MsiProperty Name="WIX_BOOTSTRAPPER_UILEVEL" Value="[WixBundleUILevel]" />
|
||||
</MsiPackage>
|
||||
</Chain>
|
||||
|
||||
</Bundle>
|
||||
</Wix>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="WixToolset.Sdk/4.0.1" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
|
||||
<Import Project="../../../Common.props" />
|
||||
<PropertyGroup>
|
||||
<DefineConstants>ProductVersion=$(TgsCoreVersion);NetMajorVersion=$(TgsNetMajorVersion);AspNetRedistUrl=$(TgsRedistUrl)</DefineConstants>
|
||||
<OutputType>Bundle</OutputType>
|
||||
<Platforms>x86</Platforms>
|
||||
<OutputName>tgstation-server-installer</OutputName>
|
||||
<!-- For debugging, this is done externally by CI -->
|
||||
<!--
|
||||
<SignOutput>true</SignOutput>
|
||||
<CertificateThumbprint>A478CB55A37A135913F6C2A19A45FD8E1A9F3633</CertificateThumbprint>
|
||||
-->
|
||||
</PropertyGroup>
|
||||
<Target Name="SignBundleEngine">
|
||||
<Message Importance="high" Text="SignBundleEngine: @(SignBundleEngine)" />
|
||||
<Exec Command='powershell.exe –NonInteractive –ExecutionPolicy Unrestricted –Command "Set-AuthenticodeSignature %(SignBundleEngine.FullPath) -Certificate (Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq \"$(CertificateThumbprint)\" }) -TimestampServer http://timestamp.digicert.com"' />
|
||||
</Target>
|
||||
<Target Name="SignBundle">
|
||||
<Message Importance="high" Text="SignBundle: @(SignBundle)" />
|
||||
<Exec Command='powershell.exe –NonInteractive –ExecutionPolicy Unrestricted –Command "Set-AuthenticodeSignature %(SignBundle.FullPath) -Certificate (Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Thumbprint -eq \"$(CertificateThumbprint)\" }) -TimestampServer http://timestamp.digicert.com"' />
|
||||
</Target>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WixToolset.Bal.wixext" Version="4.0.1" />
|
||||
<PackageReference Include="WixToolset.Netfx.wixext" Version="4.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Tgstation.Server.Host.Service.Wix\Tgstation.Server.Host.Service.Wix.wixproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup useLegacyV2RuntimeActivationPolicy="true">
|
||||
<supportedRuntime version="v4.0" />
|
||||
<supportedRuntime version="v2.0.50727" />
|
||||
</startup>
|
||||
</configuration>
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
namespace Tgstation.Server.Host.Service.Wix.SafeShutdown
|
||||
{
|
||||
using System;
|
||||
using System.ServiceProcess;
|
||||
|
||||
using Tgstation.Server.Host.Common;
|
||||
|
||||
using WixToolset.Dtf.WindowsInstaller;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for the .msi installer.
|
||||
/// </summary>
|
||||
public static class InstallationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to detach stop the existing tgstation-server service if it exists.
|
||||
/// </summary>
|
||||
/// <param name="session">The installer <see cref="Session"/>.</param>
|
||||
/// <returns>The <see cref="ActionResult"/> of the custom action.</returns>
|
||||
[CustomAction]
|
||||
public static ActionResult DetachStopTgsServiceIfRunning(Session session)
|
||||
{
|
||||
if (session == null)
|
||||
throw new ArgumentNullException(nameof(session));
|
||||
|
||||
try
|
||||
{
|
||||
session.Log("Begin DetachStopTgsServiceIfRunning");
|
||||
ServiceController serviceController = null;
|
||||
|
||||
session.Log($"Searching for {Constants.CanonicalPackageName} service...");
|
||||
foreach (var controller in ServiceController.GetServices())
|
||||
if (controller.ServiceName == Constants.CanonicalPackageName)
|
||||
{
|
||||
serviceController = controller;
|
||||
break;
|
||||
}
|
||||
else
|
||||
controller.Dispose();
|
||||
|
||||
using (serviceController)
|
||||
{
|
||||
if (serviceController == null || serviceController.Status != ServiceControllerStatus.Running)
|
||||
{
|
||||
session.Log($"{Constants.CanonicalPackageName} service not found. Continuing.");
|
||||
return ActionResult.Success;
|
||||
}
|
||||
|
||||
var commandId = PipeCommands.GetCommandId(
|
||||
PipeCommands.CommandDetachingShutdown)
|
||||
.Value;
|
||||
|
||||
session.Log($"{Constants.CanonicalPackageName} service found. Sending command \"{PipeCommands.CommandDetachingShutdown}\" ({commandId})...");
|
||||
|
||||
serviceController.ExecuteCommand(commandId);
|
||||
|
||||
session.Log($"Command sent. Waiting for {Constants.CanonicalPackageName} service to stop...");
|
||||
|
||||
serviceController.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMinutes(1));
|
||||
|
||||
var stopped = serviceController.Status == ServiceControllerStatus.Stopped;
|
||||
session.Log($"{Constants.CanonicalPackageName} stopped {(stopped ? String.Empty : "un")}successfully.");
|
||||
|
||||
return stopped
|
||||
? ActionResult.Success
|
||||
: ActionResult.NotExecuted;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
session.Log($"Exception in DetachStopTgsServiceIfRunning:{Environment.NewLine}{ex}");
|
||||
return ActionResult.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../../../Common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net2.0</TargetFramework>
|
||||
<Version>$(TgsCoreVersion)</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WixToolset.Dtf.CustomAction" Version="4.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Tgstation.Server.Host.Common\Tgstation.Server.Host.Common.csproj">
|
||||
<Private>True</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="CustomAction.config" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!--
|
||||
This file contains the declaration of all the localizable strings.
|
||||
-->
|
||||
<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
|
||||
|
||||
<String Id="DowngradeError" Value="A newer version of [ProductName] is already installed." />
|
||||
<String Id="MainServerFeatureDescription" Value="The primary application for running [ProductName]." />
|
||||
<String Id="ServerServiceDescription" Value="[ProductName] running as a Windows service." />
|
||||
<String Id="ConfigureRestartDescription" Value="Runs the tgstation-server setup wizard and restarts the service (Does not stop DreamDaemon processes)." />
|
||||
<String Id="ConfigureRestartName" Value="Configure & Restart tgstation-server" />
|
||||
</WixLocalization>
|
||||
@@ -0,0 +1,87 @@
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
|
||||
<Package Name="tgstation-server" Manufacturer="/tg/station 13" Version="$(var.ProductVersion)" UpgradeCode="36912c45-5953-4f16-ad7d-17a150ea3587" Scope="perMachine">
|
||||
<MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
|
||||
|
||||
<MediaTemplate EmbedCab="yes" />
|
||||
|
||||
<Binary Id="InstallerExtensionsBinary" SourceFile="$(var.Tgstation.Server.Host.Service.Wix.Extensions.TargetName).CA.dll" />
|
||||
|
||||
<CustomAction Id="DetachStopTgsServiceIfRunningAction" BinaryRef="InstallerExtensionsBinary" DllEntry="DetachStopTgsServiceIfRunning" Execute="deferred" />
|
||||
<CustomAction Id="RunTgsConfigure" Execute="deferred" FileRef="ServiceExecutableFile" ExeCommand="-c -p=--appsettings-base-path=[APPLICATIONDATADIRECTORY]" />
|
||||
|
||||
<Icon Id="tgs.ico" SourceFile="../../../tgs.ico" />
|
||||
|
||||
<SetProperty Id="WIX_BOOTSTRAPPER_UILEVEL" Value="[UILevel]" After="CostFinalize" Condition="WIX_BOOTSTRAPPER_UILEVEL=""" />
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="DetachStopTgsServiceIfRunningAction" Condition="UPGRADINGPRODUCTCODE OR REINSTALL" Before="StopServices" />
|
||||
<Custom Action="RunTgsConfigure" Before="StartServices" Condition="(NOT REMOVE~="ALL") AND (NOT PRODUCTIONAPPSETTINGSPRESENT) AND ((WIX_BOOTSTRAPPER_UILEVEL >= 4) OR (UILevel >= 4))" />
|
||||
<StartServices Condition="(NOT REMOVE~="ALL") AND ((WIX_BOOTSTRAPPER_UILEVEL >= 4) OR (UILevel >= 4) OR PRODUCTIONAPPSETTINGSPRESENT)"/>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<Feature Id="MainServerFeature" AllowAbsent="no" Description="!(loc.MainServerFeatureDescription)">
|
||||
<ComponentGroupRef Id="InitialHostComponentGroup"/>
|
||||
<ComponentGroupRef Id="ServiceHostWatchdogComponentGroup"/>
|
||||
<ComponentRef Id="StoreApplicationDataDirectoryPathComponent"/>
|
||||
<ComponentRef Id="ServiceHostWatchdogExeComponent"/>
|
||||
<ComponentRef Id="PurgeLibDirectoryComponent"/>
|
||||
<ComponentRef Id="BaseAppSettingsComponent"/>
|
||||
<ComponentRef Id="ProductionAppSettingsComponent"/>
|
||||
</Feature>
|
||||
|
||||
<Property Id="LIBDIRECTORY">
|
||||
<RegistrySearch Key="SOFTWARE\tgstation\tgstation-server" Root="HKLM" Type="raw" Id="StoredLibDirectory" Name="LibDirectory" />
|
||||
</Property>
|
||||
|
||||
<Property Id="APPLICATIONDATADIRECTORY">
|
||||
<RegistrySearch Key="SOFTWARE\tgstation\tgstation-server" Root="HKLM" Type="raw" Id="StoredProgramDataDirectory" Name="ProgramDataDirectory" />
|
||||
</Property>
|
||||
|
||||
<Property Id="PRODUCTIONAPPSETTINGSPRESENT">
|
||||
<DirectorySearch Path="[CommonAppDataFolder]" Depth="0" Id="ProgramDataDirectorySearcher">
|
||||
<DirectorySearch Path="!(bind.Property.ProductName)" Depth="0" Id="ApplicationDataDirectorySearcher">
|
||||
<FileSearch Name="appsettings.Production.yml" />
|
||||
</DirectorySearch>
|
||||
</DirectorySearch>
|
||||
</Property>
|
||||
|
||||
<StandardDirectory Id="ProgramFiles6432Folder">
|
||||
<Directory Id="ApplicationDirectory" Name="!(bind.Property.ProductName)">
|
||||
<Directory Id="LIBDIRECTORY" Name="lib">
|
||||
<Directory Id="DefaultLibDirectory" Name="Default" />
|
||||
<Component Id="PurgeLibDirectoryComponent" Guid="38E323E2-0404-4ACB-ACDD-80F5525538FB">
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\tgstation\tgstation-server" Name="LibDirectory" Type="string" Value="[LIBDIRECTORY]" KeyPath="yes" />
|
||||
<util:RemoveFolderEx Id="PurgeLibDirectory" On="both" Property="LIBDIRECTORY" />
|
||||
</Component>
|
||||
</Directory>
|
||||
<Component Id="ServiceHostWatchdogExeComponent" Guid="9430D56D-EEBF-4026-9EC1-693668A6EC7C" >
|
||||
<File Id="ServiceExecutableFile" Source="Tgstation.Server.Host.Service.exe" KeyPath="yes" />
|
||||
<Shortcut Advertise="yes" Arguments="-c -x -r -p=--appsettings-base-path=[APPLICATIONDATADIRECTORY]" Description="!(loc.ConfigureRestartDescription)" Name="!(loc.ConfigureRestartName)" Directory="DesktopFolder" Icon="tgs.ico" />
|
||||
<Shortcut Advertise="yes" Arguments="-c -x -r -p=--appsettings-base-path=[APPLICATIONDATADIRECTORY]" Description="!(loc.ConfigureRestartDescription)" Name="!(loc.ConfigureRestartName)" Directory="StartMenuFolder" Icon="tgs.ico" />
|
||||
<ServiceInstall Name="!(bind.Property.ProductName)" DisplayName="!(bind.Property.ProductName)" Description="!(loc.ServerServiceDescription)" Start="auto" Type="ownProcess" ErrorControl="normal" Account="LocalSystem" Vital="yes" Interactive="no" Arguments="-p=--appsettings-base-path=[APPLICATIONDATADIRECTORY]">
|
||||
<ServiceDependency Id="Tcpip" />
|
||||
<ServiceDependency Id="Dhcp" />
|
||||
<ServiceDependency Id="Dnscache" />
|
||||
</ServiceInstall>
|
||||
<ServiceControl Name="!(bind.Property.ProductName)" Remove="uninstall" Start="install" Stop="uninstall" Wait="yes" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</StandardDirectory>
|
||||
|
||||
<StandardDirectory Id="CommonAppDataFolder">
|
||||
<Directory Id="APPLICATIONDATADIRECTORY" Name="!(bind.Property.ProductName)">
|
||||
<Component Id="StoreApplicationDataDirectoryPathComponent" Guid="86B0EB79-DAC9-4325-8965-42089ABED375">
|
||||
<RegistryValue Root="HKLM" Key="SOFTWARE\tgstation\tgstation-server" Name="ProgramDataDirectory" Type="string" Value="[APPLICATIONDATADIRECTORY]" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="BaseAppSettingsComponent" Guid="" NeverOverwrite="yes">
|
||||
<File Source="../../../../src/Tgstation.Server.Host/appsettings.yml" />
|
||||
</Component>
|
||||
<Component Id="ProductionAppSettingsComponent" Guid="" NeverOverwrite="yes">
|
||||
<File Source="../../appsettings.Initial.yml" Name="appsettings.Production.yml">
|
||||
<PermissionEx Sddl="D:PAI(A;;FA;;;SY)(A;;FA;;;BA)" />
|
||||
</File>
|
||||
</Component>
|
||||
</Directory>
|
||||
</StandardDirectory>
|
||||
</Package>
|
||||
</Wix>
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<Project Sdk="WixToolset.Sdk/4.0.1">
|
||||
<Import Project="../../../Common.props" />
|
||||
<PropertyGroup>
|
||||
<DefineConstants>ProductVersion=$(TgsCoreVersion)</DefineConstants>
|
||||
<HarvestDirectorySuppressSpecificWarnings>5150;5151</HarvestDirectorySuppressSpecificWarnings>
|
||||
<Platforms>x86</Platforms>
|
||||
<OutputName>tgstation-server</OutputName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<BindPath Include="../Tgstation.Server.Host.Service.Wix.Extensions/$(Configuration)/net472" />
|
||||
<BindPath Include="../../../../artifacts" />
|
||||
<BindPath Include="../../../../artifacts/Tgstation.Server.Host" />
|
||||
<BindPath Include="../../../../artifacts/Tgstation.Server.Host.Service" />
|
||||
<HarvestDirectory Include="../../../../artifacts/Tgstation.Server.Host">
|
||||
<ComponentGroupName>InitialHostComponentGroup</ComponentGroupName>
|
||||
<DirectoryRefId>DefaultLibDirectory</DirectoryRefId>
|
||||
<SuppressCom>true</SuppressCom>
|
||||
<SuppressRegistry>true</SuppressRegistry>
|
||||
<SuppressRootDirectory>true</SuppressRootDirectory>
|
||||
</HarvestDirectory>
|
||||
<HarvestDirectory Include="../../../../artifacts/Tgstation.Server.Host.Service">
|
||||
<ComponentGroupName>ServiceHostWatchdogComponentGroup</ComponentGroupName>
|
||||
<DirectoryRefId>ApplicationDirectory</DirectoryRefId>
|
||||
<SuppressRootDirectory>true</SuppressRootDirectory>
|
||||
</HarvestDirectory>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="WixToolset.Heat" Version="4.0.1" />
|
||||
<PackageReference Include="WixToolset.Util.wixext" Version="4.0.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Tgstation.Server.Host.Service.Wix.Extensions\Tgstation.Server.Host.Service.Wix.Extensions.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,31 @@
|
||||
# Created using wingetcreate 1.2.6.0
|
||||
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.4.0.schema.json
|
||||
|
||||
PackageIdentifier: Tgstation.Server
|
||||
PackageVersion: 0.22.475 # Do not change. Set before publish by push_manifest.ps1
|
||||
Installers:
|
||||
- InstallerUrl: https://github.com/tgstation/tgstation-server/releases/download/tgstation-server-v0.22.475/tgstation-server-installer.exe # Do not change. Set before publish by push_manifest.ps1
|
||||
InstallerLocale: en-US
|
||||
Architecture: x86
|
||||
InstallerType: burn
|
||||
Scope: machine
|
||||
InstallerSha256: CF0D4D2FD042098D826A226A05A9DAA5C792318CF48FD334F7FC06AF6A8A23B1 # Do not change. Set before publish by push_manifest.ps1
|
||||
InstallModes:
|
||||
- interactive
|
||||
- silent
|
||||
- silentWithProgress
|
||||
ProductCode: '{D24887FA-3228-4509-B5F3-4E07E349F278}'
|
||||
UnsupportedOSArchitectures:
|
||||
- arm
|
||||
- arm64
|
||||
ElevationRequirement: elevatesSelf
|
||||
AppsAndFeaturesEntries:
|
||||
- DisplayName: tgstation-server
|
||||
DisplayVersion: 0.22.475 # Do not change. Set before publish by push_manifest.ps1
|
||||
Publisher: /tg/station 13
|
||||
Dependencies:
|
||||
PackageDependencies:
|
||||
- PackageIdentifier: Microsoft.DotNet.AspNetCore.6
|
||||
ReleaseDate: 2023-06-24 # Do not change. Set before publish by push_manifest.ps1
|
||||
ManifestType: installer
|
||||
ManifestVersion: 1.4.0
|
||||
@@ -0,0 +1,21 @@
|
||||
# Created using wingetcreate 1.2.6.0
|
||||
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.4.0.schema.json
|
||||
|
||||
PackageIdentifier: Tgstation.Server
|
||||
PackageVersion: 0.22.475 # Do not change. Set before publish by push_manifest.ps1
|
||||
PackageLocale: en-US
|
||||
Publisher: /tg/station 13
|
||||
PublisherUrl: https://github.com/tgstation/tgstation-server
|
||||
PublisherSupportUrl: https://github.com/tgstation/tgstation-server/discussions/categories/q-a
|
||||
Author: Dominion/Cyberboss
|
||||
PackageName: tgstation-server
|
||||
License: AGPL-3.0
|
||||
ShortDescription: A production scale tool for BYOND server management
|
||||
Moniker: tgstation-server
|
||||
Documentations:
|
||||
- DocumentLabel: README.md
|
||||
DocumentUrl: https://github.com/tgstation/tgstation-server/blob/tgstation-server-v0.22.475/README.md # Do not change. Set before publish by push_manifest.ps1
|
||||
ReleaseNotesUrl: https://github.com/tgstation/tgstation-server/releases/tag/tgstation-server-v0.22.475
|
||||
PurchaseUrl: https://github.com/sponsors/Cyberboss
|
||||
ManifestType: defaultLocale
|
||||
ManifestVersion: 1.4.0
|
||||
@@ -0,0 +1,8 @@
|
||||
# Created using wingetcreate 1.2.6.0
|
||||
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.4.0.schema.json
|
||||
|
||||
PackageIdentifier: Tgstation.Server
|
||||
PackageVersion: 0.22.475 # Do not change. Set before publish by push_manifest.ps1
|
||||
DefaultLocale: en-US
|
||||
ManifestType: version
|
||||
ManifestVersion: 1.4.0
|
||||
@@ -0,0 +1,45 @@
|
||||
# Note: This script requires that Tgstation.Server.Host and Tgstation.Server.Host.Service be built in Release configuration beforehand
|
||||
|
||||
$ErrorActionPreference="stop"
|
||||
|
||||
$startDirectory=$pwd
|
||||
try
|
||||
{
|
||||
Remove-Item -Recurse -Force artifacts -ErrorAction SilentlyContinue
|
||||
Remove-Item -Recurse -Force build/package/winget/Tgstation.Server.Host.Service.Wix/bin -ErrorAction SilentlyContinue
|
||||
Remove-Item -Recurse -Force build/package/winget/Tgstation.Server.Host.Service.Wix/obj -ErrorAction SilentlyContinue
|
||||
Remove-Item -Recurse -Force build/package/winget/Tgstation.Server.Host.Service.Wix.Bundle/bin -ErrorAction SilentlyContinue
|
||||
Remove-Item -Recurse -Force build/package/winget/Tgstation.Server.Host.Service.Wix.Bundle/obj -ErrorAction SilentlyContinue
|
||||
|
||||
[XML]$versionXML = Get-Content build/Version.props -ErrorAction Stop
|
||||
$redistUrl = $versionXML.Project.PropertyGroup.TgsRedistUrl
|
||||
|
||||
mkdir artifacts
|
||||
$previousProgressPreference = $ProgressPreference
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
try
|
||||
{
|
||||
Invoke-WebRequest -Uri $redistUrl -OutFile artifacts/hosting-bundle.exe
|
||||
} finally {
|
||||
$ProgressPreference = $previousProgressPreference
|
||||
}
|
||||
|
||||
cd src/Tgstation.Server.Host
|
||||
dotnet publish -c Release --no-build -o ../../artifacts/Tgstation.Server.Host
|
||||
if (-Not $?) {
|
||||
exit $lastexitcode
|
||||
}
|
||||
|
||||
cd ../Tgstation.Server.Host.Service
|
||||
|
||||
dotnet publish -c Release --no-build -o ../../artifacts/Tgstation.Server.Host.Service
|
||||
if (-Not $?) {
|
||||
exit $lastexitcode
|
||||
}
|
||||
|
||||
mv ../../artifacts/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.exe ../../artifacts/
|
||||
}
|
||||
finally
|
||||
{
|
||||
cd $startDirectory
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
$ErrorActionPreference="Stop"
|
||||
|
||||
[XML]$versionXML = Get-Content build/Version.props -ErrorAction Stop
|
||||
|
||||
$tgsVersion = $versionXML.Project.PropertyGroup.TgsCoreVersion
|
||||
|
||||
mkdir artifacts
|
||||
$previousProgressPreference = $ProgressPreference
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
try
|
||||
{
|
||||
Invoke-WebRequest -Uri "https://github.com/tgstation/tgstation-server/releases/download/tgstation-server-v$tgsVersion/tgstation-server-installer.exe" -OutFile "artifacts/tgstation-server-installer.exe"
|
||||
} finally {
|
||||
$ProgressPreference = $previousProgressPreference
|
||||
}
|
||||
|
||||
$installerHash = Get-FileHash -Path "artifacts/tgstation-server-installer.exe" -ErrorAction Stop # SHA256 is the default
|
||||
|
||||
cd build/package/winget/manifest
|
||||
try
|
||||
{
|
||||
$devHash = 'CF0D4D2FD042098D826A226A05A9DAA5C792318CF48FD334F7FC06AF6A8A23B1'
|
||||
$devVersion = '0.22.475'
|
||||
$devReleaseDate = '2023-06-24'
|
||||
$releaseDate = Get-Date -format "yyyy-MM-dd"
|
||||
|
||||
(Get-Content Tgstation.Server.installer.yaml -ErrorAction Stop).Replace($devHash, $installerHash.Hash).Replace($devReleaseDate, $releaseDate).Replace($devVersion, $tgsVersion) | Set-Content Tgstation.Server.installer.yaml -ErrorAction Stop
|
||||
(Get-Content Tgstation.Server.locale.en-US.yaml -ErrorAction Stop).Replace($devVersion, $tgsVersion) | Set-Content Tgstation.Server.locale.en-US.yaml -ErrorAction Stop
|
||||
(Get-Content Tgstation.Server.yaml -ErrorAction Stop).Replace($devVersion, $tgsVersion) | Set-Content Tgstation.Server.yaml -ErrorAction Stop
|
||||
|
||||
winget validate --manifest .
|
||||
if (-Not $?) {
|
||||
exit $lastexitcode
|
||||
}
|
||||
|
||||
wingetcreate submit -t $Env:WINGET_PUSH_TOKEN .
|
||||
if (-Not $?) {
|
||||
exit $lastexitcode
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
cd ../../../..
|
||||
}
|
||||
@@ -14,7 +14,6 @@ if [ ! -f $PROD_CONFIG ]; then
|
||||
fi
|
||||
|
||||
echo "$PROD_CONFIG not detected! Creating empty and running setup wizard..."
|
||||
# Important, config reloading doesn't work with symlinks
|
||||
echo "{}" > $PROD_CONFIG
|
||||
fi
|
||||
|
||||
|
||||
@@ -7,13 +7,18 @@ After=postgresql.service
|
||||
After=mssql-server.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/bin/bash /opt/tgstation-server/tgs.sh General:SetupWizardMode=Never
|
||||
Type=notify-reload
|
||||
NotifyAccess=all
|
||||
WorkingDirectory=/opt/tgstation-server
|
||||
ExecStart=/usr/bin/dotnet Tgstation.Server.Host.Console.dll --appsettings-base-path=/etc/tgstation-server --General:SetupWizardMode=Never --Internal:UsingSystemD=true
|
||||
TimeoutStartSec=600
|
||||
Restart=Always
|
||||
KillMode=process
|
||||
ReloadSignal=SIGUSR2
|
||||
RestartKillSignal=SIGUSR2
|
||||
AmbientCapabilities=CAP_SYS_NICE
|
||||
StandardOutput=null
|
||||
StandardError=null
|
||||
WatchdogSec=60
|
||||
WatchdogSignal=SIGTERM
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel level="highestAvailable" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
+2
-35
@@ -3,41 +3,6 @@
|
||||
|
||||
@tableofcontents
|
||||
|
||||
@section new_features New since TGS3 (Rewrite)
|
||||
|
||||
- Agnostic HTTP API: The replaces the WCF service calls used in TGS3. This helps avoid Windows vendor lock-in and get away from the SOAP API that literally no one understood (not even me). With it, it's much easier to expose TGS to the internet, all you need is a HTTPS reverse proxy in front of it. A rundown of the new API exists here: https://tgstation.github.io/tgstation-server/api.html.
|
||||
- Granular Access Controls: Windows users are no longer (required to be) the basis for authentication to the server. We now have database-backed users as a login option. These use a combined Basic/JWT authentication scheme with industry standard password hashing and salting. Users are fully customizable and can be given granular access to every bit of the server via the new permissions system. From changing the BYOND version, to test merging a PR, to restarting the server, every action may now be granted or revoked on a per user basis.
|
||||
- Limitation: Users can be disabled but not deleted.
|
||||
- Proper Long Running Operation Support: Server actions take a long time, from a git pull to a DreamMaker compile. TGS now internally allows for them to be run in parallel with each other and provides an audit record via the database. This is an improvement over the old system where connections had to be held open for the duration of operations.
|
||||
- Database Backend: TGS requires an SQL database to operate. This allows for much better concurrency and is just overall much cleaner than the old single json file storage blob per instance.
|
||||
- Limitation: There is a one-to-one relationship with a TGS server and a database. **DO NOT SHARE TGS DATABASES OUTSIDE OF SWARM MODE**.
|
||||
- Linux/Docker Support: TGS is Linux and docker compatible. (Note this does not mean that rust-g and BSQL work out of the box, they must be compiled using event scripts like PreCompile.sh).
|
||||
- Limitation: TGS has a dependency on the native library libgit2 which is known to cause issues on Linux. The binaries distributed with TGS are kept up to date with the upstream repository, but out of the box Linux support can't be assured in every environment. Docker is guaranteed to always work, however. See the repository for the distributed binary here: https://github.com/libgit2/libgit2sharp.nativebinaries.
|
||||
- Limitation: System based logins are not supported on Linux. https://github.com/tgstation/tgstation-server/issues/709
|
||||
- Incredibly Detailed Logging: Various log levels exist now (Trace/Debug/Info/Warning/Error/Critical) and are sanely output to a rolling file on the host. Significant improvement over having to use the Windows event viewer with TGS3. Until such a point where bugs stop copping up I'd recommend Trace logging for the main log level.
|
||||
- Historical Deployment Data: Every time code is compiled the following data is logged and stored.
|
||||
- The User that initiated it.
|
||||
- When it was started.
|
||||
- When it finished.
|
||||
- All revision information including local/remote SHAs, test merged pull requests and their SHAs.
|
||||
- The BYOND version used.
|
||||
- The DMAPI version in the binary
|
||||
- Multiple chat bots per instance: Up to 65535 as a matter of fact (who knows why I chose that number?)!
|
||||
- Automatic Chat Bot Reconnection Intervals: Set in minutes.
|
||||
- Better Error State Handling: The Server and Watchdog aren't your momma's boys anymore. Every error state will be automatically resolved or reported with recommended actions.
|
||||
- Watchdog Health Checks: An interval in seconds can now be set at which TGS will send /world/Topic() packets to DreamDaemon. If four of these are missed, the server will be rebooted. No more endless @Key Holder pings in discord (and I can finally unmute the /tg/ guild)! This feature can be disabled.
|
||||
- Better DMAPI: No longer requires injecting a .NET runtime .dll into the DreamDaemon process. DD -> TGS communication is now handled securely via BYOND's native /world/Export() API ("But Cyberboss, BYOND only supports GET requests." Who said anything about respecting HTTP standards when dealing with BYOND?).
|
||||
- Safe/Ultrasafe Security Support: Thanks to the new DMAPI, the ultrasafe and safe security levels may be used without running into BYOND's limitations. But no one really cares...
|
||||
- Private/Invisible visibility Support: Stored per instance.
|
||||
- Self Upgrading: To upgrade TGS3 you needed to download and run the installer. This was pretty seamless, but it's now even better in versions >=4 as the command to upgrade can be given straight to the API. At that point the server will handle downloading the update, detaching running DreamDaemon instances, restarting with the new version, and reattaching to them. Easier than ever patch delivery.
|
||||
- Multi-server co-operation: TGS installed on multiple machines can share the same database by using Swarm Mode. This mode keeps TGS and account details in sync on all machines.
|
||||
- OAuth 2.0 Support: Integrate with your favorite 3rd party authentication providers
|
||||
- *Gasp* TESTING: TGS currently has over 60% code coverage in automated unit and full stack integration tests. I aim to have that number ever increasing to prevent trivial mistakes. Big improvement over V3 which had... literally none...
|
||||
|
||||
Along with these features, nearly every single V3 feature has been included and possibly improved in some fashion. This includes stuff like Windows accounts for logins, and using ACLs for static file handling. The following exceptions exist but are planned for future updates:
|
||||
- Process memory/CPU diagnostic data is not generated: https://github.com/tgstation/tgstation-server/issues/611
|
||||
- Direct server announcements are no longer present but may be readded upon request.
|
||||
|
||||
@section features_list Comprehensive Feature List
|
||||
|
||||
tgstation-server is a BYOND server managment suite. It includes all the following features
|
||||
@@ -58,6 +23,8 @@ tgstation-server is a BYOND server managment suite. It includes all the followin
|
||||
- Supports Elasticsearch ingesting
|
||||
- Able to self update with user input
|
||||
- Updates are sourced from our (or anyone else's if configured to do so) GitHub releases or uploaded directly by the user.
|
||||
- Can also self update using official installation packages.
|
||||
- Able to shutdown and restart without interrupting DreamDaemon processes.
|
||||
- Swarm system to coordinate servers across systems
|
||||
- Database backend supporting several providers
|
||||
- Microsoft SQL Server
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"msbuild": {
|
||||
"Configuration": "DebugNoService",
|
||||
"EnablePackageAutoRestore" : true
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using Microsoft.Net.Http.Headers;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Properties;
|
||||
using Tgstation.Server.Common.Extensions;
|
||||
|
||||
namespace Tgstation.Server.Api
|
||||
{
|
||||
@@ -51,7 +52,7 @@ namespace Tgstation.Server.Api
|
||||
public const string OAuthAuthenticationScheme = "OAuth";
|
||||
|
||||
/// <summary>
|
||||
/// Added to <see cref="MediaTypeNames.Application"/> in netstandard2.1. Can't use because of Tgstation.Server.Migrator.
|
||||
/// Added to <see cref="MediaTypeNames.Application"/> in netstandard2.1. Can't use because of lack of .NET Framework support.
|
||||
/// </summary>
|
||||
public const string ApplicationJsonMime = "application/json";
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# TGS API Definitions
|
||||
# tgstation-server API Definitions
|
||||
|
||||
This assembly defines models and routes for communicating with TGS.
|
||||
|
||||
- Data models are stored in the [Models](./Models) directory.
|
||||
- Rights enums are stored in the [Rights](./Rights) directory.
|
||||
- API routes are defined in the [Routes.cs](./Routes.cs) file.
|
||||
This assembly defines HTTP headers, default credentials, models, rights, and routes for communicating with the [tgstation-server](http://github.com/tgstation/tgstation-server) API.
|
||||
|
||||
@@ -48,4 +48,8 @@
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="../../build/stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Tgstation.Server.Common\Tgstation.Server.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# tgstation-server Client Library
|
||||
|
||||
This library is used for accessing [tgstation-server](https://github.com/tgstation/tgstation-server) instances via .NET code.
|
||||
|
||||
## Examples
|
||||
|
||||
### Connecting to a Server:
|
||||
|
||||
```cs
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Client;
|
||||
|
||||
...
|
||||
|
||||
async ValueTask<IServerClient> CreateClientWithDefaultCredentials(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
|
||||
return await clientFactory.CreateFromLogin(
|
||||
url,
|
||||
DefaultCredentials.AdminUserName,
|
||||
DefaultCredentials.DefaultAdminUserPassword,
|
||||
cancellationToken: cancellationToken);
|
||||
}
|
||||
```
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Tgstation.Server.Api\Tgstation.Server.Api.csproj" />
|
||||
<ProjectReference Include="..\Tgstation.Server.Common\Tgstation.Server.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Api
|
||||
namespace Tgstation.Server.Common.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for the <see cref="Version"/> class.
|
||||
@@ -0,0 +1,3 @@
|
||||
# tgstation-server Common functionalty
|
||||
|
||||
This library contains common functions used by both the [tgstation-server](https://github.com/tgstation/tgstation-server) host and client.
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Tgstation.Server.Host.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant values.
|
||||
/// </summary>
|
||||
public static class Constants
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the project.
|
||||
/// </summary>
|
||||
public const string CanonicalPackageName = "tgstation-server";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace Tgstation.Server.Host.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Values able to be passed via the update file path.
|
||||
/// </summary>
|
||||
public static class PipeCommands
|
||||
{
|
||||
/// <summary>
|
||||
/// Stops the server ASAP, shutting down any running instances.
|
||||
/// </summary>
|
||||
public const string CommandStop = "stop";
|
||||
|
||||
/// <summary>
|
||||
/// Stops the server eventually, waiting for the games in any running instances to reboot.
|
||||
/// </summary>
|
||||
public const string CommandGracefulShutdown = "graceful";
|
||||
|
||||
/// <summary>
|
||||
/// Stops the server ASAP, detaching the watchdog for any running instances.
|
||||
/// </summary>
|
||||
public const string CommandDetachingShutdown = "detach";
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
/// <summary>
|
||||
/// All of the <see cref="PipeCommands"/> represented as a <see cref="System.Collections.Generic.IReadOnlyList{T}"/>.
|
||||
/// </summary>
|
||||
public static System.Collections.Generic.IReadOnlyList<string> AllCommands { get; } = new[]
|
||||
{
|
||||
CommandStop,
|
||||
CommandGracefulShutdown,
|
||||
CommandDetachingShutdown,
|
||||
};
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="int"/> value of a given <paramref name="command"/>.
|
||||
/// </summary>
|
||||
/// <param name="command">The <see cref="PipeCommands"/>.</param>
|
||||
/// <returns>The <see cref="int"/> value of the command or <see langword="null"/> if it was unrecognized.</returns>
|
||||
public static int? GetCommandId(string command)
|
||||
=> command switch
|
||||
{
|
||||
CommandStop => 128, // Windows only allows commands 128-256: https://stackoverflow.com/a/62858106
|
||||
CommandGracefulShutdown => 129,
|
||||
CommandDetachingShutdown => 130,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
<Import Project="../../build/Common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFrameworks>$(TgsFrameworkVersion);net2.0</TargetFrameworks>
|
||||
<Version>$(TgsCoreVersion)</Version>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Mono.Unix;
|
||||
using Mono.Unix.Native;
|
||||
|
||||
using Tgstation.Server.Host.Watchdog;
|
||||
|
||||
namespace Tgstation.Server.Host.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="ISignalChecker"/> for checking POSIX signals.
|
||||
/// </summary>
|
||||
sealed class PosixSignalChecker : ISignalChecker
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="PosixSignalChecker"/>.
|
||||
/// </summary>
|
||||
readonly ILogger<PosixSignalChecker> logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PosixSignalChecker"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public PosixSignalChecker(ILogger<PosixSignalChecker> logger)
|
||||
{
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CheckSignals(Func<string, (int, Task)> startChild, CancellationToken cancellationToken)
|
||||
{
|
||||
var (childPid, _) = startChild?.Invoke(null) ?? throw new ArgumentNullException(nameof(startChild));
|
||||
var signalTcs = new TaskCompletionSource<Signum>();
|
||||
async Task<Signum?> CheckSignal(Signum signum)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var unixSignal = new UnixSignal(signum);
|
||||
if (!unixSignal.IsSet)
|
||||
{
|
||||
logger.LogTrace("Waiting for {signum}...", signum);
|
||||
while (!unixSignal.IsSet)
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(250), cancellationToken);
|
||||
|
||||
logger.LogTrace("{signum} received!", signum);
|
||||
}
|
||||
else
|
||||
logger.LogDebug("{signum} has already been sent", signum);
|
||||
|
||||
signalTcs.TrySetResult(signum);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
|
||||
return signum;
|
||||
}
|
||||
|
||||
var tasks = new[]
|
||||
{
|
||||
CheckSignal(Signum.SIGUSR1),
|
||||
CheckSignal(Signum.SIGUSR2),
|
||||
};
|
||||
var completedTask = await Task.WhenAny(tasks);
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await Task.WhenAll(tasks);
|
||||
return;
|
||||
}
|
||||
|
||||
var signalReceived = await completedTask;
|
||||
logger.LogInformation("Received {signalReceived}, forwarding to main TGS process!", signalReceived);
|
||||
var result = Syscall.kill(childPid, signalReceived.Value);
|
||||
if (result != 0)
|
||||
logger.LogWarning(
|
||||
new UnixIOException(Stdlib.GetLastError()),
|
||||
"Failed to forward {signalReceived}!",
|
||||
signalReceived);
|
||||
|
||||
// forward the other signal if necessary
|
||||
await Task.WhenAll(tasks);
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
var otherTask = tasks[0] == completedTask
|
||||
? tasks[1]
|
||||
: tasks[0];
|
||||
|
||||
signalReceived = await otherTask;
|
||||
logger.LogInformation("Received {signalReceived}, forwarding to main TGS process!", signalReceived);
|
||||
result = Syscall.kill(childPid, signalReceived.Value);
|
||||
if (result != 0)
|
||||
logger.LogWarning(
|
||||
new UnixIOException(Stdlib.GetLastError()),
|
||||
"Failed to forward {signalReceived}!",
|
||||
signalReceived);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Tgstation.Server.Common.Extensions;
|
||||
using Tgstation.Server.Host.Watchdog;
|
||||
|
||||
namespace Tgstation.Server.Host.Console
|
||||
@@ -32,17 +33,28 @@ namespace Tgstation.Server.Host.Console
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments for the <see cref="Program"/>.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
internal static async Task Main(string[] args)
|
||||
internal static async Task<int> Main(string[] args)
|
||||
{
|
||||
using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
|
||||
System.Console.Title = $"tgstation-server Host Watchdog v{Assembly.GetExecutingAssembly().GetName().Version.Semver()}";
|
||||
|
||||
var arguments = new List<string>(args);
|
||||
var trace = arguments.Remove("--trace-host-watchdog");
|
||||
var debug = arguments.Remove("--debug-host-watchdog");
|
||||
|
||||
using var loggerFactory = LoggerFactory.Create(builder =>
|
||||
{
|
||||
if (trace)
|
||||
builder.SetMinimumLevel(LogLevel.Trace);
|
||||
else if (debug)
|
||||
builder.SetMinimumLevel(LogLevel.Debug);
|
||||
|
||||
builder.AddConsole();
|
||||
});
|
||||
|
||||
if (trace && debug)
|
||||
{
|
||||
loggerFactory.CreateLogger(nameof(Program)).LogCritical("Please specify only 1 of --trace-host-watchdog or --debug-host-watchdog!");
|
||||
return;
|
||||
return 2;
|
||||
}
|
||||
|
||||
using var cts = new CancellationTokenSource();
|
||||
@@ -55,7 +67,15 @@ namespace Tgstation.Server.Host.Console
|
||||
b.Cancel = true;
|
||||
cts.Cancel();
|
||||
};
|
||||
await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(false, arguments.ToArray(), cts.Token);
|
||||
|
||||
var watchdog = WatchdogFactory.CreateWatchdog(
|
||||
new PosixSignalChecker(
|
||||
loggerFactory.CreateLogger<PosixSignalChecker>()),
|
||||
loggerFactory);
|
||||
|
||||
return await watchdog.RunAsync(false, arguments.ToArray(), cts.Token)
|
||||
? 0
|
||||
: 1;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>$(TgsNetVersion)</TargetFramework>
|
||||
<TargetFramework>$(TgsFrameworkVersion)</TargetFramework>
|
||||
<Version>$(TgsCoreVersion)</Version>
|
||||
<UseAppHost>false</UseAppHost>
|
||||
<IsPackable>false</IsPackable>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<CodeAnalysisRuleSet>../../build/analyzers.ruleset</CodeAnalysisRuleSet>
|
||||
<DocumentationFile>bin/$(Configuration)/$(TargetFramework)/$(AssemblyName).xml</DocumentationFile>
|
||||
<ApplicationManifest>../../build/uac_elevation_manifest.xml</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
@@ -47,5 +49,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="../../build/stylecop.json" />
|
||||
<AdditionalFiles Include="../../build/uac_elevation_manifest.xml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
@echo off
|
||||
|
||||
dotnet %~dp0Tgstation.Server.Host.Console.dll %*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/sh -e
|
||||
|
||||
script_full_path=$(dirname "$0")
|
||||
exec dotnet "$script_full_path/Tgstation.Server.Host.Console.dll" "$@"
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2232:MarkWindowsFormsEntryPointsWithStaThread", Justification = "No don't, it breaks shit")]
|
||||
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
//
|
||||
// To add a suppression to this file, right-click the message in the
|
||||
// Code Analysis results, point to "Suppress Message", and click
|
||||
// "In Suppression File".
|
||||
// You do not need to add suppressions to this file manually.
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Tgstation.Server.Host.Service
|
||||
{
|
||||
/// <summary>
|
||||
/// Native methods used by the code.
|
||||
/// </summary>
|
||||
static class NativeMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox.
|
||||
/// </summary>
|
||||
public enum MessageBoxButtons : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// The message box contains two push buttons: Yes and No.
|
||||
/// </summary>
|
||||
YesNo = 0x00000004,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The result of a call to <see cref="MessageBox(HandleRef, string, string, MessageBoxButtons)"/>.
|
||||
/// </summary>
|
||||
public enum DialogResult : int
|
||||
{
|
||||
/// <summary>
|
||||
/// The Yes button was selected.
|
||||
/// </summary>
|
||||
Yes = 6,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Displays a modal dialog box that contains a system icon, a set of buttons, and a brief application-specific message, such as status or error information. The message box returns an integer value that indicates which button the user clicked.
|
||||
/// </summary>
|
||||
/// <param name="hWnd">A handle to the owner window of the message box to be created. If this parameter is NULL, the message box has no owner window.</param>
|
||||
/// <param name="text">The message to be displayed. If the string consists of more than one line, you can separate the lines using a carriage return and/or linefeed character between each line.</param>
|
||||
/// <param name="caption">The dialog box title. If this parameter is NULL, the default title is Error.</param>
|
||||
/// <param name="type">The <see cref="MessageBoxButtons"/>.</param>
|
||||
/// <returns>The resulting <see cref="DialogResult"/>.</returns>
|
||||
/// <remarks>See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messagebox.</remarks>
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern DialogResult MessageBox(HandleRef hWnd, string text, string caption, MessageBoxButtons type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Tgstation.Server.Host.Watchdog;
|
||||
|
||||
namespace Tgstation.Server.Host.Service
|
||||
{
|
||||
/// <summary>
|
||||
/// No-op <see cref="ISignalChecker"/>.
|
||||
/// </summary>
|
||||
sealed class NoopSignalChecker : ISignalChecker
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public Task CheckSignals(Func<string, (int, Task)> startChild, CancellationToken cancellationToken)
|
||||
{
|
||||
startChild(null);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Configuration.Install;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Security.Principal;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using McMaster.Extensions.CommandLineUtils;
|
||||
|
||||
using Microsoft.Extensions.Hosting.WindowsServices;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Tgstation.Server.Host.Common;
|
||||
using Tgstation.Server.Host.Watchdog;
|
||||
|
||||
namespace Tgstation.Server.Host.Service
|
||||
@@ -34,43 +31,50 @@ namespace Tgstation.Server.Host.Service
|
||||
/// <summary>
|
||||
/// The --uninstall or -u option.
|
||||
/// </summary>
|
||||
[Option(ShortName = "u")]
|
||||
[Option(ShortName = "u", Description = "Uninstalls ANY installed tgstation-server service >=v4.0.0")]
|
||||
public bool Uninstall { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The --detach or -x option. Valid only with <see cref="Install"/>.
|
||||
/// </summary>
|
||||
[Option(ShortName = "x", Description = "If the service has to stop, detach any running DreamDaemon processes beforehand. Only supported on versions >=5.13.0")]
|
||||
public bool Detach { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The --restart or -r option.
|
||||
/// </summary>
|
||||
[Option(ShortName = "r", Description = "Stop and restart the tgstation-server service")]
|
||||
public bool Restart { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The --install or -i option.
|
||||
/// </summary>
|
||||
[Option(ShortName = "i")]
|
||||
[Option(ShortName = "i", Description = "Installs this executable as the tgstation-server Windows service")]
|
||||
public bool Install { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The --force or -f option.
|
||||
/// </summary>
|
||||
[Option(ShortName = "f", Description = "Automatically agree to uninstall prompts")]
|
||||
public bool Force { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The --silent or -s option.
|
||||
/// </summary>
|
||||
[Option(ShortName = "s", Description = "Suppresses console output from the host watchdog")]
|
||||
public bool Silent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The --configure or -c option.
|
||||
/// </summary>
|
||||
[Option(ShortName = "c")]
|
||||
[Option(ShortName = "c", Description = "Runs the TGS setup wizard")]
|
||||
public bool Configure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The --trace or -t option. Enables trace logs.
|
||||
/// The --passthroughargs or -p option.
|
||||
/// </summary>
|
||||
[Option(ShortName = "t")]
|
||||
public bool Trace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The --debug or -d option. Enables debug logs.
|
||||
/// </summary>
|
||||
[Option(ShortName = "d")]
|
||||
public bool Debug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Check if the running user is a system administrator.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true"/> if the running user is a system administrator, <see langword="false"/> otherwise.</returns>
|
||||
static bool IsAdministrator()
|
||||
{
|
||||
var user = WindowsIdentity.GetCurrent();
|
||||
var principal = new WindowsPrincipal(user);
|
||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
[Option(ShortName = "p", Description = "Arguments passed to main host process")]
|
||||
public string PassthroughArgs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Entrypoint for the application.
|
||||
@@ -80,47 +84,30 @@ namespace Tgstation.Server.Host.Service
|
||||
static Task<int> Main(string[] args) => CommandLineApplication.ExecuteAsync<Program>(args);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to install the TGS Service.
|
||||
/// Runs sc.exe to either uninstall a given <paramref name="serviceToUninstall"/> or install the running <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
static void RunServiceInstall()
|
||||
/// <param name="serviceToUninstall">The name of a service to uninstall.</param>
|
||||
/// <returns>A <see cref="ValueTask"/> representing the running operation.</returns>
|
||||
static async ValueTask InvokeSC(string serviceToUninstall)
|
||||
{
|
||||
// First check if the service already exists
|
||||
if (Environment.UserInteractive)
|
||||
foreach (ServiceController sc in ServiceController.GetServices())
|
||||
if (sc.ServiceName == "tgstation-server" || sc.ServiceName == "tgstation-server-4")
|
||||
{
|
||||
DialogResult result = MessageBox.Show($"You already have another TGS service installed ({sc.ServiceName}). Would you like to uninstall it now? Pressing \"No\" will cancel this install.", "TGS Service", MessageBoxButtons.YesNo);
|
||||
if (result != DialogResult.Yes)
|
||||
return; // is this needed after exit?
|
||||
using var process = new Process();
|
||||
process.StartInfo.FileName = "C:/Windows/System32/sc.exe";
|
||||
|
||||
// Stop it first to give it some cleanup time
|
||||
if (sc.Status == ServiceControllerStatus.Running)
|
||||
{
|
||||
sc.Stop();
|
||||
sc.WaitForStatus(ServiceControllerStatus.Stopped);
|
||||
}
|
||||
var fullPathToAssembly = Path.GetFullPath(
|
||||
Assembly.GetExecutingAssembly().Location);
|
||||
|
||||
// And remove it
|
||||
using var serviceInstaller = new ServiceInstaller();
|
||||
serviceInstaller.Context = new InstallContext($"old-{sc.ServiceName}-uninstall.log", null);
|
||||
serviceInstaller.ServiceName = sc.ServiceName;
|
||||
serviceInstaller.Uninstall(null);
|
||||
}
|
||||
var assemblyDirectory = Path.GetDirectoryName(fullPathToAssembly);
|
||||
var assemblyNameWithoutExtension = Path.GetFileNameWithoutExtension(fullPathToAssembly);
|
||||
var exePath = Path.Combine(assemblyDirectory, $"{assemblyNameWithoutExtension}.exe");
|
||||
|
||||
using var processInstaller = new ServiceProcessInstaller();
|
||||
using var installer = new ServiceInstaller();
|
||||
processInstaller.Account = ServiceAccount.LocalSystem;
|
||||
process.StartInfo.Arguments = serviceToUninstall == null
|
||||
? $"create tgstation-server binPath=\"{exePath}\" start=auto depend=Tcpip/Dhcp/Dnscache"
|
||||
: $"delete {serviceToUninstall}";
|
||||
process.StartInfo.CreateNoWindow = false;
|
||||
|
||||
installer.Context = new InstallContext("tgs-install.log", new string[] { String.Format(CultureInfo.InvariantCulture, "/assemblypath={0}", Assembly.GetEntryAssembly().Location) });
|
||||
installer.Description = "/tg/station 13 server running as a windows service";
|
||||
installer.DisplayName = "/tg/station server";
|
||||
installer.StartType = ServiceStartMode.Automatic;
|
||||
installer.ServicesDependedOn = new string[] { "Tcpip", "Dhcp", "Dnscache" };
|
||||
installer.ServiceName = ServerService.Name;
|
||||
installer.Parent = processInstaller;
|
||||
process.Start();
|
||||
|
||||
var state = new ListDictionary();
|
||||
installer.Install(state);
|
||||
await process.WaitForExitAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -129,67 +116,158 @@ namespace Tgstation.Server.Host.Service
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
public async Task OnExecuteAsync()
|
||||
{
|
||||
if (Environment.UserInteractive)
|
||||
{
|
||||
if (!Install && !Uninstall && !Configure)
|
||||
var standardRun = !Install && !Uninstall && !Configure;
|
||||
if (standardRun)
|
||||
if (!Silent && !WindowsServiceHelpers.IsWindowsService())
|
||||
{
|
||||
var result = MessageBox.Show("You are running the TGS windows service executable directly. It should only be run by the service control manager. Would you like to install and configure the service in this location?", "TGS Service", MessageBoxButtons.YesNo);
|
||||
if (result != DialogResult.Yes)
|
||||
var result = NativeMethods.MessageBox(
|
||||
default,
|
||||
"You are running the TGS windows service executable directly. It should only be run by the service control manager. Would you like to install and configure the service in this location?",
|
||||
"TGS Service",
|
||||
NativeMethods.MessageBoxButtons.YesNo);
|
||||
|
||||
if (result != NativeMethods.DialogResult.Yes)
|
||||
return;
|
||||
|
||||
Install = true;
|
||||
Configure = true;
|
||||
}
|
||||
|
||||
if (!IsAdministrator())
|
||||
{
|
||||
// try to restart as admin
|
||||
// its windows, first arg is .exe name guaranteed
|
||||
var exe = Environment.GetCommandLineArgs().First();
|
||||
var startInfo = new ProcessStartInfo
|
||||
else
|
||||
using (var service = new ServerService(WatchdogFactory, GetPassthroughArgs(), LogLevel.Trace))
|
||||
{
|
||||
UseShellExecute = true,
|
||||
Verb = "runas",
|
||||
Arguments = String.Format(CultureInfo.InvariantCulture, "{0} {1}", Install ? "-i" : Uninstall ? "-u" : String.Empty, Configure ? "-c" : String.Empty),
|
||||
FileName = exe,
|
||||
WorkingDirectory = Environment.CurrentDirectory,
|
||||
};
|
||||
using (Process.Start(startInfo))
|
||||
service.Run();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Configure)
|
||||
await RunConfigure(CancellationToken.None); // DCT: None available
|
||||
|
||||
bool stopped = false;
|
||||
if (Uninstall)
|
||||
{
|
||||
foreach (ServiceController sc in ServiceController.GetServices())
|
||||
{
|
||||
bool match;
|
||||
using (sc)
|
||||
{
|
||||
match = sc.ServiceName == ServerService.Name;
|
||||
if (match)
|
||||
RestartService(sc);
|
||||
}
|
||||
|
||||
if (match)
|
||||
{
|
||||
await InvokeSC(ServerService.Name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
stopped = true;
|
||||
}
|
||||
|
||||
if (Install)
|
||||
{
|
||||
if (Uninstall)
|
||||
return; // oh no, it's retarded...
|
||||
stopped |= await RunServiceInstall();
|
||||
|
||||
RunServiceInstall();
|
||||
if (Restart)
|
||||
foreach (ServiceController sc in ServiceController.GetServices())
|
||||
using (sc)
|
||||
if (sc.ServiceName == ServerService.Name)
|
||||
{
|
||||
if (!stopped)
|
||||
RestartService(sc);
|
||||
|
||||
if (Configure)
|
||||
{
|
||||
Console.WriteLine("For this first run we'll launch the console runner so you may use the setup wizard.");
|
||||
Console.WriteLine("If it starts successfully, feel free to close it and then start the service from the Windows control panel.");
|
||||
}
|
||||
}
|
||||
else if (Uninstall)
|
||||
using (var installer = new ServiceInstaller())
|
||||
{
|
||||
installer.Context = new InstallContext("tgs-uninstall.log", null);
|
||||
installer.ServiceName = ServerService.Name;
|
||||
installer.Uninstall(null);
|
||||
}
|
||||
else if (!Configure)
|
||||
sc.Start();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to install the TGS Service.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in <see langword="true"/> if the service was stopped or detached as a result, <see langword="false"/> otherwise.</returns>
|
||||
async ValueTask<bool> RunServiceInstall()
|
||||
{
|
||||
// First check if the service already exists
|
||||
bool serviceStopped = false;
|
||||
if (Force || !WindowsServiceHelpers.IsWindowsService())
|
||||
foreach (ServiceController sc in ServiceController.GetServices())
|
||||
using (sc)
|
||||
{
|
||||
var serviceName = sc.ServiceName;
|
||||
if (serviceName == ServerService.Name || serviceName == "tgstation-server-4")
|
||||
{
|
||||
NativeMethods.DialogResult result = !Force
|
||||
? NativeMethods.MessageBox(
|
||||
default,
|
||||
$"You already have another TGS service installed ({sc.ServiceName}). Would you like to uninstall it now? Pressing \"No\" will cancel this install.",
|
||||
"TGS Service",
|
||||
NativeMethods.MessageBoxButtons.YesNo)
|
||||
: NativeMethods.DialogResult.Yes;
|
||||
if (result != NativeMethods.DialogResult.Yes)
|
||||
return false; // is this needed after exit?
|
||||
|
||||
// Stop it first to give it some cleanup time
|
||||
RestartService(sc);
|
||||
|
||||
// And remove it
|
||||
await InvokeSC(sc.ServiceName);
|
||||
}
|
||||
}
|
||||
|
||||
await InvokeSC(null);
|
||||
|
||||
return serviceStopped;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restarts a service using a given <paramref name="serviceController"/>.
|
||||
/// </summary>
|
||||
/// <param name="serviceController">The <see cref="ServiceController"/> for the service to restart.</param>
|
||||
void RestartService(ServiceController serviceController)
|
||||
{
|
||||
if (serviceController.Status != ServiceControllerStatus.Running)
|
||||
return;
|
||||
|
||||
var stop = !Detach;
|
||||
if (!stop)
|
||||
{
|
||||
using var service = new ServerService(WatchdogFactory, Trace ? LogLevel.Trace : Debug ? LogLevel.Debug : LogLevel.Information);
|
||||
ServiceBase.Run(service);
|
||||
serviceController.ExecuteCommand(
|
||||
PipeCommands.GetCommandId(
|
||||
PipeCommands.CommandDetachingShutdown)
|
||||
.Value);
|
||||
serviceController.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromSeconds(30));
|
||||
if (serviceController.Status != ServiceControllerStatus.Stopped)
|
||||
stop = true;
|
||||
}
|
||||
|
||||
if (Configure)
|
||||
if (stop)
|
||||
{
|
||||
using var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
|
||||
await WatchdogFactory.CreateWatchdog(loggerFactory)
|
||||
.RunAsync(true, Array.Empty<string>(), CancellationToken.None); // DCT: None available
|
||||
serviceController.Stop();
|
||||
serviceController.WaitForStatus(ServiceControllerStatus.Stopped);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the host application with the setup wizard.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
async Task RunConfigure(CancellationToken cancellationToken)
|
||||
{
|
||||
using var loggerFactory = LoggerFactory.Create(builder =>
|
||||
{
|
||||
if (!Silent)
|
||||
builder.AddConsole();
|
||||
});
|
||||
|
||||
var watchdog = WatchdogFactory.CreateWatchdog(new NoopSignalChecker(), loggerFactory);
|
||||
await watchdog.RunAsync(true, GetPassthroughArgs(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Format <see cref="PassthroughArgs"/> into an <see cref="Array"/>.
|
||||
/// </summary>
|
||||
/// <returns><see cref="PassthroughArgs"/> formatted as a <see cref="string"/> <see cref="Array"/>.</returns>
|
||||
string[] GetPassthroughArgs() => PassthroughArgs?.Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Linq;
|
||||
using System.ServiceProcess;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.EventLog;
|
||||
|
||||
using Tgstation.Server.Host.Common;
|
||||
using Tgstation.Server.Host.Watchdog;
|
||||
|
||||
namespace Tgstation.Server.Host.Service
|
||||
@@ -15,12 +20,12 @@ namespace Tgstation.Server.Host.Service
|
||||
/// <summary>
|
||||
/// Represents a <see cref="IWatchdog"/> as a <see cref="ServiceBase"/>.
|
||||
/// </summary>
|
||||
sealed class ServerService : ServiceBase
|
||||
sealed class ServerService : ServiceBase, ISignalChecker
|
||||
{
|
||||
/// <summary>
|
||||
/// The canonical windows service name.
|
||||
/// </summary>
|
||||
public const string Name = "tgstation-server";
|
||||
public const string Name = Constants.CanonicalPackageName;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IWatchdog"/> for the <see cref="ServerService"/>.
|
||||
@@ -28,17 +33,27 @@ namespace Tgstation.Server.Host.Service
|
||||
readonly IWatchdogFactory watchdogFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum <see cref="Microsoft.Extensions.Logging.LogLevel"/> for the <see cref="EventLog"/>.
|
||||
/// The <see cref="Array"/> of command line arguments the service was invoked with.
|
||||
/// </summary>
|
||||
readonly string[] commandLineArguments;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum <see cref="LogLevel"/> for the <see cref="EventLog"/>.
|
||||
/// </summary>
|
||||
readonly LogLevel minimumLogLevel;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILoggerFactory"/> used by the service.
|
||||
/// The <see cref="ILoggerFactory"/> used by the <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
ILoggerFactory loggerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Task"/> that represents the running service.
|
||||
/// The <see cref="ILogger"/> for the <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
ILogger<ServerService> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Task"/> that represents the running <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
Task watchdogTask;
|
||||
|
||||
@@ -47,26 +62,71 @@ namespace Tgstation.Server.Host.Service
|
||||
/// </summary>
|
||||
CancellationTokenSource cancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="AnonymousPipeServerStream"/> the server process is using.
|
||||
/// </summary>
|
||||
AnonymousPipeServerStream pipeServer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="watchdogFactory">The value of <see cref="watchdogFactory"/>.</param>
|
||||
/// <param name="minimumLogLevel">The minimum <see cref="Microsoft.Extensions.Logging.LogLevel"/> to record in the event log.</param>
|
||||
public ServerService(IWatchdogFactory watchdogFactory, LogLevel minimumLogLevel)
|
||||
/// <param name="commandLineArguments">The value of <see cref="commandLineArguments"/>.</param>
|
||||
/// <param name="minimumLogLevel">The minimum <see cref="LogLevel"/> to record in the event log.</param>
|
||||
public ServerService(IWatchdogFactory watchdogFactory, string[] commandLineArguments, LogLevel minimumLogLevel)
|
||||
{
|
||||
this.watchdogFactory = watchdogFactory ?? throw new ArgumentNullException(nameof(watchdogFactory));
|
||||
this.commandLineArguments = commandLineArguments ?? throw new ArgumentNullException(nameof(commandLineArguments));
|
||||
this.minimumLogLevel = minimumLogLevel;
|
||||
ServiceName = Name;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CheckSignals(Func<string, (int, Task)> startChildAndGetPid, CancellationToken cancellationToken)
|
||||
{
|
||||
using (pipeServer = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable))
|
||||
{
|
||||
var (_, lifetimeTask) = startChildAndGetPid($"--Internal:CommandPipe={pipeServer.GetClientHandleAsString()}");
|
||||
pipeServer.DisposeLocalCopyOfClientHandle();
|
||||
await lifetimeTask;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the <see cref="ServerService"/>.
|
||||
/// </summary>
|
||||
public void Run() => Run(this);
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
loggerFactory?.Dispose();
|
||||
cancellationTokenSource?.Dispose();
|
||||
if (disposing)
|
||||
{
|
||||
loggerFactory?.Dispose();
|
||||
cancellationTokenSource?.Dispose();
|
||||
pipeServer?.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnCustomCommand(int command)
|
||||
{
|
||||
var commandsToCheck = PipeCommands.AllCommands;
|
||||
foreach (var stringCommand in commandsToCheck)
|
||||
{
|
||||
var commandId = PipeCommands.GetCommandId(stringCommand);
|
||||
if (command == commandId)
|
||||
{
|
||||
SendCommandToUpdatePath(stringCommand);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogWarning("Received unknown service command: {command}", command);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnStart(string[] args)
|
||||
{
|
||||
@@ -79,14 +139,24 @@ namespace Tgstation.Server.Host.Service
|
||||
SourceName = EventLog.Source,
|
||||
Filter = (message, logLevel) => logLevel >= minimumLogLevel,
|
||||
}));
|
||||
|
||||
logger = loggerFactory.CreateLogger<ServerService>();
|
||||
}
|
||||
|
||||
var watchdog = watchdogFactory.CreateWatchdog(loggerFactory);
|
||||
var watchdog = watchdogFactory.CreateWatchdog(this, loggerFactory);
|
||||
|
||||
cancellationTokenSource?.Dispose();
|
||||
cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
watchdogTask = RunWatchdog(watchdog, args, cancellationTokenSource.Token);
|
||||
var newArgs = new List<string>(commandLineArguments.Length + args.Length + 1)
|
||||
{
|
||||
"--General:SetupWizardMode=Never",
|
||||
};
|
||||
|
||||
newArgs.AddRange(commandLineArguments);
|
||||
newArgs.AddRange(args);
|
||||
|
||||
watchdogTask = RunWatchdog(watchdog, newArgs.ToArray(), cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -105,24 +175,60 @@ namespace Tgstation.Server.Host.Service
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
async Task RunWatchdog(IWatchdog watchdog, string[] args, CancellationToken cancellationToken)
|
||||
{
|
||||
await watchdog.RunAsync(false, args, cancellationTokenSource.Token);
|
||||
await watchdog.RunAsync(false, args, cancellationToken);
|
||||
|
||||
void StopServiceAsync()
|
||||
async void StopServiceAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
Task.Run(Stop, cancellationToken);
|
||||
await Task.Run(Stop, cancellationToken); // DCT intentional
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
logger.LogTrace(ex, "Stopping service cancelled!");
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
EventLog.WriteEntry(String.Format(CultureInfo.InvariantCulture, "Error stopping service! Exception: {0}", e));
|
||||
logger.LogError(ex, "Error stopping service!");
|
||||
}
|
||||
}
|
||||
|
||||
StopServiceAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a command to the main server process.
|
||||
/// </summary>
|
||||
/// <param name="command">One of the <see cref="PipeCommands"/>.</param>
|
||||
void SendCommandToUpdatePath(string command)
|
||||
{
|
||||
var localPipeServer = pipeServer;
|
||||
if (localPipeServer == null)
|
||||
{
|
||||
logger.LogWarning("Unable to send command \"{command}\" to main server process. Is the service running?", command);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogDebug("Send command: {command}", command);
|
||||
try
|
||||
{
|
||||
var encoding = Encoding.UTF8;
|
||||
using var streamWriter = new StreamWriter(
|
||||
localPipeServer,
|
||||
encoding,
|
||||
PipeCommands
|
||||
.AllCommands
|
||||
.Select(
|
||||
command => encoding.GetByteCount(
|
||||
command + Environment.NewLine))
|
||||
.Max(),
|
||||
true);
|
||||
streamWriter.WriteLine(command);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error attempting to send command \"{command}\"", command);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
<Import Project="../../build/Common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<RuntimeIdentifier>win</RuntimeIdentifier>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>$(TgsFrameworkVersion)-windows</TargetFramework>
|
||||
<Version>$(TgsCoreVersion)</Version>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<!-- DO NOT ENABLE THIS, It makes the service require the desktop runtime instead of the ASP NET Core Hosting Bundle -->
|
||||
<UseWindowsForms>false</UseWindowsForms>
|
||||
<CodeAnalysisRuleSet>../../build/analyzers.ruleset</CodeAnalysisRuleSet>
|
||||
<DocumentationFile>bin/$(Configuration)/$(TargetFramework)/$(RuntimeIdentifier)/$(AssemblyName).xml</DocumentationFile>
|
||||
<ApplicationIcon>../../build/tgs.ico</ApplicationIcon>
|
||||
<ApplicationManifest>../../build/uac_elevation_manifest.xml</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
@@ -23,6 +26,7 @@
|
||||
<ItemGroup>
|
||||
<!-- Usage: Command line argument support -->
|
||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="7.0.1" />
|
||||
<!-- Usage: Windows event log logging plugin -->
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="7.0.0" />
|
||||
<!-- Usage: Console logging plugin -->
|
||||
@@ -34,6 +38,7 @@
|
||||
</PackageReference>
|
||||
<!-- Usage: OS identification -->
|
||||
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="7.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -42,11 +47,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="../../build/stylecop.json" />
|
||||
<AdditionalFiles Include="../../build/uac_elevation_manifest.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Configuration.Install" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Watchdog
|
||||
{
|
||||
/// <summary>
|
||||
/// For relaying signals received to the host process.
|
||||
/// </summary>
|
||||
public interface ISignalChecker
|
||||
{
|
||||
/// <summary>
|
||||
/// Relays signals received to the host process.
|
||||
/// </summary>
|
||||
/// <param name="startChild">An <see cref="Func{TResult}"/> to start the main process. It accepts an optional additional command line argument as a paramter and returns it's <see cref="System.Diagnostics.Process.Id"/> and lifetime <see cref="Task"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task CheckSignals(Func<string, (int, Task)> startChild, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
/// <param name="runConfigure">If the <see cref="IWatchdog"/> should just run the host configuration wizard and exit.</param>
|
||||
/// <param name="args">The arguments for the <see cref="IWatchdog"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task RunAsync(bool runConfigure, string[] args, CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if there were no errors, <see langword="false"/> otherwise.</returns>
|
||||
Task<bool> RunAsync(bool runConfigure, string[] args, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
/// <summary>
|
||||
/// Create a <see cref="IWatchdog"/>.
|
||||
/// </summary>
|
||||
/// <param name="signalChecker">The <see cref="ISignalChecker"/> to use for relaying signals.</param>
|
||||
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> to use for error reporting.</param>
|
||||
/// <returns>A new <see cref="IWatchdog"/>.</returns>
|
||||
IWatchdog CreateWatchdog(ILoggerFactory loggerFactory);
|
||||
IWatchdog CreateWatchdog(ISignalChecker signalChecker, ILoggerFactory loggerFactory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Import Project="../../build/Common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>$(TgsFrameworkVersion)</TargetFramework>
|
||||
<DebugType>Full</DebugType>
|
||||
<AddSyntheticProjectReferencesForSolutionDependencies>false</AddSyntheticProjectReferencesForSolutionDependencies>
|
||||
<Version>$(TgsHostWatchdogVersion)</Version>
|
||||
@@ -44,6 +44,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Tgstation.Server.Common\Tgstation.Server.Common.csproj" />
|
||||
<ProjectReference Include="..\Tgstation.Server.Host.Common\Tgstation.Server.Host.Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -10,10 +10,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Mono.Unix;
|
||||
using Mono.Unix.Native;
|
||||
|
||||
using Tgstation.Server.Common.Extensions;
|
||||
using Tgstation.Server.Host.Common;
|
||||
|
||||
namespace Tgstation.Server.Host.Watchdog
|
||||
@@ -22,6 +19,11 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
/// <remarks>This <see langword="class"/> is a HACK but it works. Try not to break it if you wish to change it. Remember, this code doesn't get updated with the rest of the server.</remarks>
|
||||
sealed class Watchdog : IWatchdog
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ISignalChecker"/> for the <see cref="Watchdog"/>.
|
||||
/// </summary>
|
||||
readonly ISignalChecker signalChecker;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="Watchdog"/>.
|
||||
/// </summary>
|
||||
@@ -30,16 +32,18 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Watchdog"/> class.
|
||||
/// </summary>
|
||||
/// <param name="signalChecker">The value of <see cref="signalChecker"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public Watchdog(ILogger<Watchdog> logger)
|
||||
public Watchdog(ISignalChecker signalChecker, ILogger<Watchdog> logger)
|
||||
{
|
||||
this.signalChecker = signalChecker ?? throw new ArgumentNullException(nameof(signalChecker));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
#pragma warning disable CA1502 // TODO: Decomplexify
|
||||
#pragma warning disable CA1506
|
||||
public async Task RunAsync(bool runConfigure, string[] args, CancellationToken cancellationToken)
|
||||
public async Task<bool> RunAsync(bool runConfigure, string[] args, CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogInformation("Host watchdog starting...");
|
||||
int currentProcessId;
|
||||
@@ -55,7 +59,7 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
if (dotnetPath == default)
|
||||
{
|
||||
logger.LogCritical("Unable to locate dotnet executable in PATH! Please ensure the .NET Core runtime is installed and is in your PATH!");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.LogInformation("Detected dotnet executable at {dotnetPath}", dotnetPath);
|
||||
@@ -71,15 +75,16 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
{
|
||||
// VS special tactics
|
||||
// just copy the shit where it belongs
|
||||
Directory.Delete(assemblyStoragePath, true);
|
||||
if (Directory.Exists(assemblyStoragePath))
|
||||
Directory.Delete(assemblyStoragePath, true);
|
||||
Directory.CreateDirectory(defaultAssemblyPath);
|
||||
|
||||
var sourcePath = "../../../../Tgstation.Server.Host/bin/Debug/net6.0";
|
||||
foreach (string dirPath in Directory.GetDirectories(sourcePath, "*", SearchOption.AllDirectories))
|
||||
Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath));
|
||||
Directory.CreateDirectory(dirPath.Replace(sourcePath, defaultAssemblyPath, StringComparison.Ordinal));
|
||||
|
||||
foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories))
|
||||
File.Copy(newPath, newPath.Replace(sourcePath, defaultAssemblyPath), true);
|
||||
File.Copy(newPath, newPath.Replace(sourcePath, defaultAssemblyPath, StringComparison.Ordinal), true);
|
||||
|
||||
const string AppSettingsYaml = "appsettings.yml";
|
||||
var rootYaml = Path.Combine(rootLocation, AppSettingsYaml);
|
||||
@@ -92,19 +97,19 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
var assemblyName = String.Join(".", nameof(Tgstation), nameof(Server), nameof(Host), "dll");
|
||||
var assemblyPath = Path.Combine(defaultAssemblyPath, assemblyName);
|
||||
|
||||
if (assemblyPath.Contains("\""))
|
||||
if (assemblyPath.Contains('"', StringComparison.Ordinal))
|
||||
{
|
||||
logger.LogCritical("Running from paths with \"'s in the name is not supported!");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!File.Exists(assemblyPath))
|
||||
{
|
||||
logger.LogCritical("Unable to locate host assembly!");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var watchdogVersion = executingAssembly.GetName().Version.ToString();
|
||||
var watchdogVersion = executingAssembly.GetName().Version.Semver().ToString();
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
using (logger.BeginScope("Host invocation"))
|
||||
@@ -128,8 +133,8 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
|
||||
if (runConfigure)
|
||||
{
|
||||
logger.LogInformation("Running configuration check and wizard if necessary...");
|
||||
arguments.Add("General:SetupWizardMode=Only");
|
||||
logger.LogInformation("Running configuration check and wizard...");
|
||||
arguments.Add("--General:SetupWizardMode=Only");
|
||||
}
|
||||
|
||||
arguments.AddRange(args);
|
||||
@@ -145,13 +150,20 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
};
|
||||
process.EnableRaisingEvents = true;
|
||||
|
||||
logger.LogInformation("Launching host...");
|
||||
|
||||
var killedHostProcess = false;
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
var childPid = process.Id;
|
||||
var processTask = tcs.Task;
|
||||
(int, Task) StartProcess(string additionalArg)
|
||||
{
|
||||
if (additionalArg != null)
|
||||
process.StartInfo.Arguments += $" {additionalArg}";
|
||||
|
||||
logger.LogInformation("Launching host with arguments: {arguments}", process.StartInfo.Arguments);
|
||||
|
||||
process.Start();
|
||||
return (process.Id, processTask);
|
||||
}
|
||||
|
||||
using (var processCts = new CancellationTokenSource())
|
||||
using (processCts.Token.Register(() => tcs.TrySetResult(null)))
|
||||
@@ -178,62 +190,17 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
}
|
||||
}))
|
||||
{
|
||||
var processTask = tcs.Task;
|
||||
while (!processTask.IsCompleted)
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
var checkerTask = signalChecker.CheckSignals(StartProcess, cts.Token);
|
||||
try
|
||||
{
|
||||
var signalTcs = new TaskCompletionSource<Signum>();
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
async Task CheckSignal(Signum signum)
|
||||
{
|
||||
if (isWindows)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
using var unixSignal = new UnixSignal(signum);
|
||||
if (!unixSignal.IsSet)
|
||||
{
|
||||
logger.LogTrace("Waiting for {signum}...", signum);
|
||||
while (!unixSignal.IsSet)
|
||||
await Task.Delay(TimeSpan.FromMilliseconds(250), cts.Token);
|
||||
|
||||
logger.LogTrace("{signum} received!", signum);
|
||||
}
|
||||
else
|
||||
logger.LogDebug("{signum} has already been sent", signum);
|
||||
|
||||
signalTcs.TrySetResult(signum);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
var checkerTask = Task.WhenAll(
|
||||
CheckSignal(Signum.SIGUSR1),
|
||||
CheckSignal(Signum.SIGUSR2));
|
||||
try
|
||||
{
|
||||
var signalTask = signalTcs.Task;
|
||||
|
||||
var completedTask = await Task.WhenAny(processTask, signalTask);
|
||||
if (completedTask == signalTask)
|
||||
{
|
||||
var signalReceived = await signalTask;
|
||||
logger.LogInformation("Received {signalReceived}, forwarding to main TGS process!", signalReceived);
|
||||
var result = Syscall.kill(childPid, signalReceived);
|
||||
if (result != 0)
|
||||
logger.LogWarning(
|
||||
new UnixIOException(Stdlib.GetLastError()),
|
||||
"Failed to forward {signalReceived}!",
|
||||
signalReceived);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
cts.Cancel();
|
||||
await checkerTask;
|
||||
}
|
||||
await processTask;
|
||||
}
|
||||
finally
|
||||
{
|
||||
cts.Cancel();
|
||||
await checkerTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,8 +219,19 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
process.WaitForExit();
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
catch (InvalidOperationException ex2)
|
||||
{
|
||||
logger.LogWarning(ex2, "Error killing host process!");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(updateDirectory))
|
||||
File.Delete(updateDirectory);
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
logger.LogWarning(ex2, "Error deleting comms file!");
|
||||
}
|
||||
|
||||
logger.LogInformation("Host exited!");
|
||||
@@ -261,14 +239,14 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
|
||||
if (runConfigure)
|
||||
{
|
||||
logger.LogInformation("Exiting due to configuration check...");
|
||||
return;
|
||||
logger.LogInformation("Exiting due to configure intent...");
|
||||
return true;
|
||||
}
|
||||
|
||||
switch ((HostExitCode)process.ExitCode)
|
||||
{
|
||||
case HostExitCode.CompleteExecution:
|
||||
return;
|
||||
return true;
|
||||
case HostExitCode.RestartRequested:
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
logger.LogInformation("Watchdog will restart host..."); // just a restart
|
||||
@@ -364,11 +342,14 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Host watchdog error!");
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
logger.LogInformation("Host watchdog exiting...");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#pragma warning restore CA1502
|
||||
#pragma warning restore CA1506
|
||||
@@ -388,10 +369,22 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
if (isWindows)
|
||||
{
|
||||
exeName += ".exe";
|
||||
enumerator = paths;
|
||||
enumerator = new List<string>(paths)
|
||||
{
|
||||
"C:/Program Files/dotnet",
|
||||
"C:/Program Files (x86)/dotnet",
|
||||
};
|
||||
}
|
||||
else
|
||||
enumerator = paths.Select(x => x.Split(':')).SelectMany(x => x);
|
||||
enumerator = paths
|
||||
.Select(x => x.Split(':'))
|
||||
.SelectMany(x => x)
|
||||
.Concat(new List<string>(2)
|
||||
{
|
||||
"/usr/bin",
|
||||
"/usr/share/bin",
|
||||
"/usr/local/share/dotnet",
|
||||
});
|
||||
|
||||
enumerator = enumerator.Select(x => Path.Combine(x, exeName));
|
||||
|
||||
|
||||
@@ -8,6 +8,10 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
public sealed class WatchdogFactory : IWatchdogFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IWatchdog CreateWatchdog(ILoggerFactory loggerFactory) => new Watchdog(loggerFactory?.CreateLogger<Watchdog>() ?? throw new ArgumentNullException(nameof(loggerFactory)));
|
||||
public IWatchdog CreateWatchdog(
|
||||
ISignalChecker signalChecker,
|
||||
ILoggerFactory loggerFactory) => new Watchdog(
|
||||
signalChecker ?? throw new ArgumentNullException(nameof(signalChecker)),
|
||||
loggerFactory?.CreateLogger<Watchdog>() ?? throw new ArgumentNullException(nameof(loggerFactory)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +165,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
ActiveVersion?.ToString(),
|
||||
stringVersion,
|
||||
},
|
||||
false,
|
||||
cancellationToken);
|
||||
|
||||
ActiveVersion = version;
|
||||
@@ -234,7 +235,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
await Task.WhenAny(
|
||||
containerTask,
|
||||
activeVersionUpdate)
|
||||
.WithToken(cancellationToken);
|
||||
.WaitAsync(cancellationToken);
|
||||
|
||||
if (containerTask.IsCompleted)
|
||||
logger.LogTrace("All BYOND locks for {version} are gone", version);
|
||||
@@ -301,26 +302,28 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
|
||||
var activeVersionBytesTask = GetActiveVersion();
|
||||
|
||||
using (await SemaphoreSlimContext.Lock(UserFilesSemaphore, cancellationToken))
|
||||
{
|
||||
// Create local cfg directory in case it doesn't exist
|
||||
var localCfgDirectory = ioManager.ConcatPath(
|
||||
byondInstaller.PathToUserByondFolder,
|
||||
CfgDirectoryName);
|
||||
await ioManager.CreateDirectory(
|
||||
localCfgDirectory,
|
||||
cancellationToken);
|
||||
|
||||
// Delete trusted.txt so it doesn't grow too large
|
||||
var trustedFilePath =
|
||||
ioManager.ConcatPath(
|
||||
var byondDir = byondInstaller.PathToUserByondFolder;
|
||||
if (byondDir != null)
|
||||
using (await SemaphoreSlimContext.Lock(UserFilesSemaphore, cancellationToken))
|
||||
{
|
||||
// Create local cfg directory in case it doesn't exist
|
||||
var localCfgDirectory = ioManager.ConcatPath(
|
||||
byondDir,
|
||||
CfgDirectoryName);
|
||||
await ioManager.CreateDirectory(
|
||||
localCfgDirectory,
|
||||
TrustedDmbFileName);
|
||||
logger.LogTrace("Deleting trusted .dmbs file {trustedFilePath}", trustedFilePath);
|
||||
await ioManager.DeleteFile(
|
||||
trustedFilePath,
|
||||
cancellationToken);
|
||||
}
|
||||
cancellationToken);
|
||||
|
||||
// Delete trusted.txt so it doesn't grow too large
|
||||
var trustedFilePath =
|
||||
ioManager.ConcatPath(
|
||||
localCfgDirectory,
|
||||
TrustedDmbFileName);
|
||||
logger.LogTrace("Deleting trusted .dmbs file {trustedFilePath}", trustedFilePath);
|
||||
await ioManager.DeleteFile(
|
||||
trustedFilePath,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
await ioManager.CreateDirectory(DefaultIOManager.CurrentDirectory, cancellationToken);
|
||||
var directories = await ioManager.GetDirectories(DefaultIOManager.CurrentDirectory, cancellationToken);
|
||||
@@ -452,7 +455,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
if (neededForLock && !installation.InstallationTask.IsCompleted)
|
||||
logger.LogWarning("The required BYOND version ({version}) is not readily available! We will have to wait for it to install.", version);
|
||||
|
||||
await installation.InstallationTask.WithToken(cancellationToken);
|
||||
await installation.InstallationTask.WaitAsync(cancellationToken);
|
||||
return installLock;
|
||||
}
|
||||
|
||||
@@ -475,7 +478,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
progressReporter.StageName = "Running event";
|
||||
|
||||
var versionString = version.ToString();
|
||||
await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List<string> { versionString }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.ByondInstallStart, new List<string> { versionString }, false, cancellationToken);
|
||||
|
||||
await InstallVersionFiles(progressReporter, version, customVersionStream, cancellationToken);
|
||||
|
||||
@@ -484,7 +487,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is not OperationCanceledException)
|
||||
await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List<string> { ex.Message }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.ByondInstallFail, new List<string> { ex.Message }, false, cancellationToken);
|
||||
|
||||
lock (installedVersions)
|
||||
installedVersions.Remove(version);
|
||||
@@ -619,8 +622,15 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
async Task TrustDmbPath(string fullDmbPath, CancellationToken cancellationToken)
|
||||
{
|
||||
var byondDir = byondInstaller.PathToUserByondFolder;
|
||||
if (String.IsNullOrWhiteSpace(byondDir))
|
||||
{
|
||||
logger.LogTrace("No relevant user BYOND directory to install a \"{fileName}\" in", TrustedDmbFileName);
|
||||
return;
|
||||
}
|
||||
|
||||
var trustedFilePath = ioManager.ConcatPath(
|
||||
byondInstaller.PathToUserByondFolder,
|
||||
byondDir,
|
||||
CfgDirectoryName,
|
||||
TrustedDmbFileName);
|
||||
|
||||
@@ -636,9 +646,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
trustedFileText = $"{trustedFileText.Trim()}{Environment.NewLine}";
|
||||
}
|
||||
else
|
||||
{
|
||||
trustedFileText = String.Empty;
|
||||
}
|
||||
|
||||
if (trustedFileText.Contains(fullDmbPath, StringComparison.Ordinal))
|
||||
return;
|
||||
|
||||
@@ -107,7 +107,11 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
sessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions));
|
||||
|
||||
PathToUserByondFolder = IOManager.ResolvePath(IOManager.ConcatPath(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "BYOND"));
|
||||
var documentsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
||||
if (String.IsNullOrWhiteSpace(documentsDirectory))
|
||||
PathToUserByondFolder = null; // happens with the service account
|
||||
else
|
||||
PathToUserByondFolder = IOManager.ResolvePath(IOManager.ConcatPath(documentsDirectory, "BYOND"));
|
||||
|
||||
semaphore = new SemaphoreSlim(1);
|
||||
installedDirectX = false;
|
||||
|
||||
@@ -482,7 +482,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
if (waitingForInitialConnection)
|
||||
{
|
||||
logger.LogTrace("Waiting for initial chat bot connections before updating tracking contexts...");
|
||||
await initialProviderConnectionsTask.WithToken(cancellationToken);
|
||||
await initialProviderConnectionsTask.WaitAsync(cancellationToken);
|
||||
}
|
||||
|
||||
List<Task> tasks;
|
||||
@@ -932,7 +932,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
if (messageTasks.Count == 0)
|
||||
{
|
||||
logger.LogTrace("No providers active, pausing messsage monitoring...");
|
||||
await updatedTask.WithToken(cancellationToken);
|
||||
await updatedTask.WaitAsync(cancellationToken);
|
||||
logger.LogTrace("Resuming message monitoring...");
|
||||
continue;
|
||||
}
|
||||
@@ -1073,7 +1073,7 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
{
|
||||
var cancellationToken = handlerCts.Token;
|
||||
if (waitForConnections)
|
||||
await initialProviderConnectionsTask.WithToken(cancellationToken);
|
||||
await initialProviderConnectionsTask.WaitAsync(cancellationToken);
|
||||
|
||||
await SendMessage(
|
||||
channelIdsFactory(),
|
||||
|
||||
@@ -377,7 +377,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
cancellationToken,
|
||||
DefaultIOManager.BlockingTaskCreationOptions,
|
||||
TaskScheduler.Current)
|
||||
.WithToken(cancellationToken);
|
||||
.WaitAsync(cancellationToken);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
@@ -584,7 +584,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
cancellationToken,
|
||||
TaskCreationOptions.None,
|
||||
TaskScheduler.Current)
|
||||
.WithToken(cancellationToken);
|
||||
.WaitAsync(cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Run SASL authentication on <see cref="client"/>.
|
||||
|
||||
@@ -149,7 +149,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await nextMessage.Task.WithToken(cancellationToken);
|
||||
await nextMessage.Task.WaitAsync(cancellationToken);
|
||||
lock (messageQueue)
|
||||
if (messageQueue.Count > 0)
|
||||
{
|
||||
|
||||
@@ -448,7 +448,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
async Task DeleteCompileJobContent(string directory, CancellationToken cancellationToken)
|
||||
{
|
||||
// Then call the cleanup event, waiting here first
|
||||
await eventConsumer.HandleEvent(EventType.DeploymentCleanup, new List<string> { ioManager.ResolvePath(directory) }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.DeploymentCleanup, new List<string> { ioManager.ResolvePath(directory) }, true, cancellationToken);
|
||||
await ioManager.DeleteDirectory(directory, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +386,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
repoName,
|
||||
cancellationToken);
|
||||
|
||||
var eventTask = eventConsumer.HandleEvent(EventType.DeploymentComplete, Enumerable.Empty<string>(), cancellationToken);
|
||||
var eventTask = eventConsumer.HandleEvent(EventType.DeploymentComplete, Enumerable.Empty<string>(), false, cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -537,7 +537,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
{
|
||||
// DCT: Cancellation token is for job, delaying here is fine
|
||||
progressReporter.StageName = "Running CompileCancelled event";
|
||||
await eventConsumer.HandleEvent(EventType.CompileCancelled, Enumerable.Empty<string>(), CancellationToken.None);
|
||||
await eventConsumer.HandleEvent(EventType.CompileCancelled, Enumerable.Empty<string>(), true, CancellationToken.None);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
@@ -594,6 +594,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
repoOrigin.ToString(),
|
||||
$"{byondLock.Version.Major}.{byondLock.Version.Minor}",
|
||||
},
|
||||
true,
|
||||
cancellationToken);
|
||||
|
||||
// determine the dme
|
||||
@@ -632,6 +633,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
repoOrigin.ToString(),
|
||||
$"{byondLock.Version.Major}.{byondLock.Version.Minor}",
|
||||
},
|
||||
true,
|
||||
cancellationToken);
|
||||
|
||||
// run compiler
|
||||
@@ -672,6 +674,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
exitCode == 0 ? "1" : "0",
|
||||
byondVersion.ToString(),
|
||||
},
|
||||
true,
|
||||
cancellationToken);
|
||||
throw;
|
||||
}
|
||||
@@ -684,6 +687,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
resolvedOutputDirectory,
|
||||
byondVersion.ToString(),
|
||||
},
|
||||
true,
|
||||
cancellationToken);
|
||||
|
||||
logger.LogTrace("Applying static game file symlinks...");
|
||||
@@ -803,10 +807,10 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
using (var provider = new TemporaryDmbProvider(ioManager.ResolvePath(job.DirectoryName.ToString()), String.Concat(job.DmeName, DmbExtension), job))
|
||||
await using (var controller = await sessionControllerFactory.LaunchNew(provider, byondLock, launchParameters, true, cancellationToken))
|
||||
{
|
||||
var launchResult = await controller.LaunchResult.WithToken(cancellationToken);
|
||||
var launchResult = await controller.LaunchResult.WaitAsync(cancellationToken);
|
||||
|
||||
if (launchResult.StartupTime.HasValue)
|
||||
await controller.Lifetime.WithToken(cancellationToken);
|
||||
await controller.Lifetime.WaitAsync(cancellationToken);
|
||||
|
||||
if (!controller.Lifetime.IsCompleted)
|
||||
await controller.DisposeAsync();
|
||||
@@ -958,7 +962,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
try
|
||||
{
|
||||
// DCT: None available
|
||||
await eventConsumer.HandleEvent(EventType.DeploymentCleanup, new List<string> { jobPath }, CancellationToken.None);
|
||||
await eventConsumer.HandleEvent(EventType.DeploymentCleanup, new List<string> { jobPath }, true, CancellationToken.None);
|
||||
await ioManager.DeleteDirectory(jobPath, CancellationToken.None);
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
+20
-13
@@ -127,9 +127,9 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
|
||||
Logger.LogTrace("In-progress deployment status created");
|
||||
}
|
||||
catch (ApiException ex)
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
Logger.LogWarning(ex, "Unable to create deployment!");
|
||||
Logger.LogWarning(ex, "Unable to create GitHub deployment!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
compileJob.GitHubRepoId = await repositoryIdTask;
|
||||
Logger.LogTrace("Set GitHub ID as {gitHubRepoId}", compileJob.GitHubRepoId);
|
||||
}
|
||||
catch (RateLimitExceededException ex) when (!repositorySettings.CreateGitHubDeployments.Value)
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
Logger.LogWarning(ex, "Unable to set compile job repository ID!");
|
||||
}
|
||||
@@ -252,9 +252,9 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
{
|
||||
await gitHubService.CommentOnIssue(remoteRepositoryOwner, remoteRepositoryName, comment, testMergeNumber, cancellationToken);
|
||||
}
|
||||
catch (ApiException e)
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
Logger.LogWarning(e, "Error posting GitHub comment!");
|
||||
Logger.LogWarning(ex, "Error posting GitHub comment!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,14 +336,21 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
|
||||
var gitHubService = gitHubServiceFactory.CreateService(gitHubAccessToken);
|
||||
|
||||
await gitHubService.CreateDeploymentStatus(
|
||||
new NewDeploymentStatus(deploymentState)
|
||||
{
|
||||
Description = description,
|
||||
},
|
||||
compileJob.GitHubRepoId.Value,
|
||||
compileJob.GitHubDeploymentId.Value,
|
||||
cancellationToken);
|
||||
try
|
||||
{
|
||||
await gitHubService.CreateDeploymentStatus(
|
||||
new NewDeploymentStatus(deploymentState)
|
||||
{
|
||||
Description = description,
|
||||
},
|
||||
compileJob.GitHubRepoId.Value,
|
||||
compileJob.GitHubDeploymentId.Value,
|
||||
cancellationToken);
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
Logger.LogWarning(ex, "Error updating GitHub deployment!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-10
@@ -11,7 +11,6 @@ using GitLabApiClient.Models.Notes.Requests;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
@@ -59,7 +58,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
.GetAsync(
|
||||
$"{repository.RemoteRepositoryOwner}/{repository.RemoteRepositoryName}",
|
||||
x.TestMerge.Number)
|
||||
.WithToken(cancellationToken));
|
||||
.WaitAsync(cancellationToken));
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(tasks);
|
||||
@@ -120,7 +119,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task CommentOnTestMergeSource(
|
||||
protected override async Task CommentOnTestMergeSource(
|
||||
RepositorySettings repositorySettings,
|
||||
string remoteRepositoryOwner,
|
||||
string remoteRepositoryName,
|
||||
@@ -132,13 +131,20 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
? new GitLabClient(GitLabRemoteFeatures.GitLabUrl, repositorySettings.AccessToken)
|
||||
: new GitLabClient(GitLabRemoteFeatures.GitLabUrl);
|
||||
|
||||
return client
|
||||
.MergeRequests
|
||||
.CreateNoteAsync(
|
||||
$"{remoteRepositoryOwner}/{remoteRepositoryName}",
|
||||
testMergeNumber,
|
||||
new CreateMergeRequestNoteRequest(comment))
|
||||
.WithToken(cancellationToken);
|
||||
try
|
||||
{
|
||||
await client
|
||||
.MergeRequests
|
||||
.CreateNoteAsync(
|
||||
$"{remoteRepositoryOwner}/{remoteRepositoryName}",
|
||||
testMergeNumber,
|
||||
new CreateMergeRequestNoteRequest(comment))
|
||||
.WaitAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
Logger.LogWarning(ex, "Error posting GitHub comment!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -31,15 +31,15 @@ namespace Tgstation.Server.Host.Components.Events
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task HandleEvent(EventType eventType, IEnumerable<string> parameters, CancellationToken cancellationToken)
|
||||
public async Task HandleEvent(EventType eventType, IEnumerable<string> parameters, bool deploymentPipeline, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(parameters);
|
||||
|
||||
if (watchdog == null)
|
||||
throw new InvalidOperationException("EventConsumer used without watchdog set!");
|
||||
|
||||
var scriptTask = configuration.HandleEvent(eventType, parameters, cancellationToken);
|
||||
await watchdog.HandleEvent(eventType, parameters, cancellationToken);
|
||||
var scriptTask = configuration.HandleEvent(eventType, parameters, deploymentPipeline, cancellationToken);
|
||||
await watchdog.HandleEvent(eventType, parameters, deploymentPipeline, cancellationToken);
|
||||
await scriptTask;
|
||||
}
|
||||
|
||||
@@ -49,11 +49,10 @@ namespace Tgstation.Server.Host.Components.Events
|
||||
/// <param name="watchdog">The value of <see cref="watchdog"/>.</param>
|
||||
public void SetWatchdog(IWatchdog watchdog)
|
||||
{
|
||||
#pragma warning disable IDE0016 // Use 'throw' expression
|
||||
ArgumentNullException.ThrowIfNull(watchdog);
|
||||
#pragma warning restore IDE0016 // Use 'throw' expression
|
||||
if (this.watchdog != null)
|
||||
throw new InvalidOperationException("watchdog already set!");
|
||||
|
||||
this.watchdog = watchdog;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ namespace Tgstation.Server.Host.Components.Events
|
||||
/// </summary>
|
||||
/// <param name="eventType">The <see cref="EventType"/>.</param>
|
||||
/// <param name="parameters">An <see cref="IEnumerable{T}"/> of <see cref="string"/> parameters for <paramref name="eventType"/>.</param>
|
||||
/// <param name="deploymentPipeline">If this event is part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task HandleEvent(EventType eventType, IEnumerable<string> parameters, CancellationToken cancellationToken);
|
||||
Task HandleEvent(EventType eventType, IEnumerable<string> parameters, bool deploymentPipeline, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,9 +308,10 @@ namespace Tgstation.Server.Host.Components
|
||||
|
||||
// the main point of auto update is to pull the remote
|
||||
await repo.FetchOrigin(
|
||||
NextProgressReporter("Fetch Origin"),
|
||||
repositorySettings.AccessUser,
|
||||
repositorySettings.AccessToken,
|
||||
NextProgressReporter("Fetch Origin"),
|
||||
true,
|
||||
cancellationToken);
|
||||
|
||||
var hasDbChanges = false;
|
||||
@@ -382,9 +383,10 @@ namespace Tgstation.Server.Host.Components
|
||||
await UpdateRevInfo(repo.Head, false, null);
|
||||
|
||||
var result = await repo.MergeOrigin(
|
||||
NextProgressReporter("Merge Origin"),
|
||||
repositorySettings.CommitterName,
|
||||
repositorySettings.CommitterEmail,
|
||||
NextProgressReporter("Merge Origin"),
|
||||
true,
|
||||
cancellationToken);
|
||||
|
||||
var preserveTestMerges = repositorySettings.AutoUpdatesKeepTestMerges.Value;
|
||||
@@ -430,10 +432,11 @@ namespace Tgstation.Server.Host.Components
|
||||
const string StageName = "Resetting to origin...";
|
||||
logger.LogTrace(StageName);
|
||||
await repo.ResetToOrigin(
|
||||
NextProgressReporter(StageName),
|
||||
repositorySettings.AccessUser,
|
||||
repositorySettings.AccessToken,
|
||||
repositorySettings.UpdateSubmodules.Value,
|
||||
NextProgressReporter(StageName),
|
||||
true,
|
||||
cancellationToken);
|
||||
|
||||
var currentHead = repo.Head;
|
||||
@@ -453,12 +456,13 @@ namespace Tgstation.Server.Host.Components
|
||||
if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head && (shouldSyncTracked || repositorySettings.PushTestMergeCommits.Value))
|
||||
{
|
||||
var pushedOrigin = await repo.Sychronize(
|
||||
NextProgressReporter("Synchronize"),
|
||||
repositorySettings.AccessUser,
|
||||
repositorySettings.AccessToken,
|
||||
repositorySettings.CommitterName,
|
||||
repositorySettings.CommitterEmail,
|
||||
NextProgressReporter("Synchronize"),
|
||||
shouldSyncTracked,
|
||||
true,
|
||||
cancellationToken);
|
||||
var currentHead = repo.Head;
|
||||
if (currentHead != currentRevInfo.CommitSha)
|
||||
@@ -494,7 +498,7 @@ namespace Tgstation.Server.Host.Components
|
||||
{
|
||||
await asyncDelayer.Delay(TimeSpan.FromMinutes(minutes > Int32.MaxValue ? Int32.MaxValue : minutes), cancellationToken);
|
||||
logger.LogInformation("Beginning auto update...");
|
||||
await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, Enumerable.Empty<string>(), cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.InstanceAutoUpdateStart, Enumerable.Empty<string>(), true, cancellationToken);
|
||||
try
|
||||
{
|
||||
var repositoryUpdateJob = new Job
|
||||
|
||||
@@ -281,7 +281,8 @@ namespace Tgstation.Server.Host.Components
|
||||
platformIdentifier,
|
||||
fileTransferService,
|
||||
loggerFactory.CreateLogger<StaticFiles.Configuration>(),
|
||||
generalConfiguration);
|
||||
generalConfiguration,
|
||||
sessionConfiguration);
|
||||
var eventConsumer = new EventConsumer(configuration);
|
||||
var repoManager = new RepositoryManager(
|
||||
repositoryFactory,
|
||||
|
||||
@@ -87,6 +87,11 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
readonly ISwarmServiceController swarmServiceController;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IConsole"/> for the <see cref="InstanceManager"/>.
|
||||
/// </summary>
|
||||
readonly IConsole console;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="InstanceManager"/>.
|
||||
/// </summary>
|
||||
@@ -132,6 +137,11 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
readonly CancellationTokenSource shutdownCancellationTokenSource;
|
||||
|
||||
/// <summary>
|
||||
/// The original <see cref="IConsole.Title"/> of <see cref="console"/>.
|
||||
/// </summary>
|
||||
readonly string originalConsoleTitle;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Task"/> returned by <see cref="Initialize(CancellationToken)"/>.
|
||||
/// </summary>
|
||||
@@ -155,6 +165,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="asyncDelayer">The value of <see cref="asyncDelayer"/>.</param>
|
||||
/// <param name="serverPortProvider">The value of <see cref="serverPortProvider"/>.</param>
|
||||
/// <param name="swarmServiceController">The value of <see cref="swarmServiceController"/>.</param>
|
||||
/// <param name="console">The value of <see cref="console"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="swarmConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="swarmConfiguration"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
@@ -169,6 +180,7 @@ namespace Tgstation.Server.Host.Components
|
||||
IAsyncDelayer asyncDelayer,
|
||||
IServerPortProvider serverPortProvider,
|
||||
ISwarmServiceController swarmServiceController,
|
||||
IConsole console,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptions<SwarmConfiguration> swarmConfigurationOptions,
|
||||
ILogger<InstanceManager> logger)
|
||||
@@ -183,10 +195,13 @@ namespace Tgstation.Server.Host.Components
|
||||
this.asyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer));
|
||||
this.serverPortProvider = serverPortProvider ?? throw new ArgumentNullException(nameof(serverPortProvider));
|
||||
this.swarmServiceController = swarmServiceController ?? throw new ArgumentNullException(nameof(swarmServiceController));
|
||||
this.console = console ?? throw new ArgumentNullException(nameof(console));
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
originalConsoleTitle = console.Title;
|
||||
|
||||
instances = new Dictionary<long, ReferenceCountingContainer<IInstance, InstanceWrapper>>();
|
||||
bridgeHandlers = new Dictionary<string, IBridgeHandler>();
|
||||
readyTcs = new TaskCompletionSource();
|
||||
@@ -323,7 +338,7 @@ namespace Tgstation.Server.Host.Components
|
||||
|
||||
try
|
||||
{
|
||||
await container.OnZeroReferences.WithToken(cancellationToken);
|
||||
await container.OnZeroReferences.WaitAsync(cancellationToken);
|
||||
|
||||
// we are the one responsible for cancelling his jobs
|
||||
var tasks = new List<Task>();
|
||||
@@ -426,42 +441,50 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <inheritdoc />
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using (cancellationToken.Register(shutdownCancellationTokenSource.Cancel))
|
||||
try
|
||||
{
|
||||
logger.LogDebug("Stopping instance manager...");
|
||||
|
||||
if (!startupTask.IsCompleted)
|
||||
try
|
||||
{
|
||||
using (cancellationToken.Register(shutdownCancellationTokenSource.Cancel))
|
||||
try
|
||||
{
|
||||
logger.LogTrace("Interrupting startup task...");
|
||||
startupCancellationTokenSource.Cancel();
|
||||
await startupTask;
|
||||
logger.LogDebug("Stopping instance manager...");
|
||||
|
||||
if (!startupTask.IsCompleted)
|
||||
{
|
||||
logger.LogTrace("Interrupting startup task...");
|
||||
startupCancellationTokenSource.Cancel();
|
||||
await startupTask;
|
||||
}
|
||||
|
||||
var instanceFactoryStopTask = instanceFactory.StopAsync(cancellationToken);
|
||||
await jobService.StopAsync(cancellationToken);
|
||||
|
||||
async Task OfflineInstanceImmediate(IInstance instance, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await instance.StopAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Instance shutdown exception!");
|
||||
}
|
||||
}
|
||||
|
||||
await Task.WhenAll(instances.Select(x => OfflineInstanceImmediate(x.Value.Instance, cancellationToken)));
|
||||
await instanceFactoryStopTask;
|
||||
|
||||
await swarmServiceController.Shutdown(cancellationToken);
|
||||
}
|
||||
|
||||
var instanceFactoryStopTask = instanceFactory.StopAsync(cancellationToken);
|
||||
await jobService.StopAsync(cancellationToken);
|
||||
|
||||
async Task OfflineInstanceImmediate(IInstance instance, CancellationToken cancellationToken)
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
await instance.StopAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Instance shutdown exception!");
|
||||
}
|
||||
if (originalConsoleTitle != null)
|
||||
console.Title = originalConsoleTitle;
|
||||
}
|
||||
|
||||
await Task.WhenAll(instances.Select(x => OfflineInstanceImmediate(x.Value.Instance, cancellationToken)));
|
||||
await instanceFactoryStopTask;
|
||||
|
||||
await swarmServiceController.Shutdown(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Instance manager stop exception!");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogCritical(ex, "Instance manager stop exception!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -535,6 +558,7 @@ namespace Tgstation.Server.Host.Components
|
||||
try
|
||||
{
|
||||
logger.LogInformation("{versionString}", assemblyInformationProvider.VersionString);
|
||||
console.Title = assemblyInformationProvider.VersionString;
|
||||
|
||||
CheckSystemCompatibility();
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
var mr = await client
|
||||
.MergeRequests
|
||||
.GetAsync($"{RemoteRepositoryOwner}/{RemoteRepositoryName}", parameters.Number)
|
||||
.WithToken(cancellationToken);
|
||||
.WaitAsync(cancellationToken);
|
||||
|
||||
var revisionToUse = parameters.TargetCommitSha == null
|
||||
|| mr.Sha.StartsWith(parameters.TargetCommitSha, StringComparison.OrdinalIgnoreCase)
|
||||
|
||||
@@ -83,31 +83,35 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <summary>
|
||||
/// Fetch commits from the origin repository.
|
||||
/// </summary>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="username">The username to fetch from the origin repository.</param>
|
||||
/// <param name="password">The password to fetch from the origin repository.</param>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="deploymentPipeline">If any events created should be marked as part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task FetchOrigin(
|
||||
JobProgressReporter progressReporter,
|
||||
string username,
|
||||
string password,
|
||||
JobProgressReporter progressReporter,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Requires the current HEAD to be a tracked reference. Hard resets the reference to what it tracks on the origin repository.
|
||||
/// </summary>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="username">The username used for fetching from submodule repositories.</param>
|
||||
/// <param name="password">The password used for fetching from submodule repositories.</param>
|
||||
/// <param name="updateSubmodules">If a submodule update should be attempted after the merge.</param>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="deploymentPipeline">If any events created should be marked as part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the SHA of the new HEAD.</returns>
|
||||
Task ResetToOrigin(
|
||||
JobProgressReporter progressReporter,
|
||||
string username,
|
||||
string password,
|
||||
bool updateSubmodules,
|
||||
JobProgressReporter progressReporter,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
@@ -122,31 +126,39 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <summary>
|
||||
/// Requires the current HEAD to be a tracked reference. Merges the reference to what it tracks on the origin repository.
|
||||
/// </summary>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="committerName">The name of the merge committer.</param>
|
||||
/// <param name="committerEmail">The e-mail of the merge committer.</param>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="deploymentPipeline">If any events created should be marked as part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Nullable{T}"/> <see cref="bool"/> representing the merge result that is <see langword="true"/> after a fast forward, <see langword="false"/> on a merge or up to date, <see langword="null"/> on a conflict.</returns>
|
||||
Task<bool?> MergeOrigin(string committerName, string committerEmail, JobProgressReporter progressReporter, CancellationToken cancellationToken);
|
||||
Task<bool?> MergeOrigin(
|
||||
JobProgressReporter progressReporter,
|
||||
string committerName,
|
||||
string committerEmail,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Runs the synchronize event script and attempts to push any changes made to the <see cref="IRepository"/> if on a tracked branch.
|
||||
/// </summary>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="username">The username to fetch from the origin repository.</param>
|
||||
/// <param name="password">The password to fetch from the origin repository.</param>
|
||||
/// <param name="committerName">The name of the potential committer.</param>
|
||||
/// <param name="committerEmail">The e-mail of the potential committer.</param>
|
||||
/// <param name="progressReporter">The <see cref="JobProgressReporter"/> to report progress of the operation.</param>
|
||||
/// <param name="synchronizeTrackedBranch">If the synchronizations should be made to the tracked reference as opposed to a temporary branch.</param>
|
||||
/// <param name="deploymentPipeline">If any events created should be marked as part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if commits were pushed to the tracked origin reference, <see langword="false"/> otherwise.</returns>
|
||||
Task<bool> Sychronize(
|
||||
JobProgressReporter progressReporter,
|
||||
string username,
|
||||
string password,
|
||||
string committerName,
|
||||
string committerEmail,
|
||||
JobProgressReporter progressReporter,
|
||||
bool synchronizeTrackedBranch,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -333,6 +333,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
await eventConsumer.HandleEvent(
|
||||
EventType.RepoMergeConflict,
|
||||
arguments,
|
||||
false,
|
||||
cancellationToken);
|
||||
return new TestMergeResult
|
||||
{
|
||||
@@ -359,6 +360,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
progressReporter.CreateSection("Update Submodules", progressFactor),
|
||||
username,
|
||||
password,
|
||||
false,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -371,6 +373,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
testMergeParameters.TargetCommitSha,
|
||||
testMergeParameters.Comment,
|
||||
},
|
||||
false,
|
||||
cancellationToken);
|
||||
|
||||
return new TestMergeResult
|
||||
@@ -392,7 +395,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
ArgumentNullException.ThrowIfNull(committish);
|
||||
ArgumentNullException.ThrowIfNull(progressReporter);
|
||||
logger.LogDebug("Checkout object: {committish}...", committish);
|
||||
await eventConsumer.HandleEvent(EventType.RepoCheckout, new List<string> { committish }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.RepoCheckout, new List<string> { committish }, false, cancellationToken);
|
||||
await Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
@@ -411,15 +414,21 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
progressReporter.CreateSection(null, 1.0 / 3),
|
||||
username,
|
||||
password,
|
||||
false,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task FetchOrigin(string username, string password, JobProgressReporter progressReporter, CancellationToken cancellationToken)
|
||||
public async Task FetchOrigin(
|
||||
JobProgressReporter progressReporter,
|
||||
string username,
|
||||
string password,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(progressReporter);
|
||||
logger.LogDebug("Fetch origin...");
|
||||
await eventConsumer.HandleEvent(EventType.RepoFetch, Enumerable.Empty<string>(), cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.RepoFetch, Enumerable.Empty<string>(), deploymentPipeline, cancellationToken);
|
||||
await Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
@@ -458,10 +467,11 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ResetToOrigin(
|
||||
JobProgressReporter progressReporter,
|
||||
string username,
|
||||
string password,
|
||||
bool updateSubmodules,
|
||||
JobProgressReporter progressReporter,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(progressReporter);
|
||||
@@ -469,7 +479,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
throw new JobException(ErrorCode.RepoReferenceRequired);
|
||||
logger.LogTrace("Reset to origin...");
|
||||
var trackedBranch = libGitRepo.Head.TrackedBranch;
|
||||
await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List<string> { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(EventType.RepoResetOrigin, new List<string> { trackedBranch.FriendlyName, trackedBranch.Tip.Sha }, deploymentPipeline, cancellationToken);
|
||||
await ResetToSha(
|
||||
trackedBranch.Tip.Sha,
|
||||
progressReporter.CreateSection(null, updateSubmodules ? 2.0 / 3 : 1.0),
|
||||
@@ -480,6 +490,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
progressReporter.CreateSection(null, 1.0 / 3),
|
||||
username,
|
||||
password,
|
||||
deploymentPipeline,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
@@ -547,9 +558,10 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool?> MergeOrigin(
|
||||
JobProgressReporter progressReporter,
|
||||
string committerName,
|
||||
string committerEmail,
|
||||
JobProgressReporter progressReporter,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(progressReporter);
|
||||
@@ -606,7 +618,17 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
if (result.Status == MergeStatus.Conflicts)
|
||||
{
|
||||
await eventConsumer.HandleEvent(EventType.RepoMergeConflict, new List<string> { oldTip.Sha, trackedBranch.Tip.Sha, oldHead.FriendlyName ?? UnknownReference, trackedBranch.FriendlyName }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(
|
||||
EventType.RepoMergeConflict,
|
||||
new List<string>
|
||||
{
|
||||
oldTip.Sha,
|
||||
trackedBranch.Tip.Sha,
|
||||
oldHead.FriendlyName ?? UnknownReference,
|
||||
trackedBranch.FriendlyName,
|
||||
},
|
||||
deploymentPipeline,
|
||||
cancellationToken);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -615,12 +637,13 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<bool> Sychronize(
|
||||
JobProgressReporter progressReporter,
|
||||
string username,
|
||||
string password,
|
||||
string committerName,
|
||||
string committerEmail,
|
||||
JobProgressReporter progressReporter,
|
||||
bool synchronizeTrackedBranch,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(committerName);
|
||||
@@ -661,6 +684,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
ioMananger.ResolvePath(),
|
||||
},
|
||||
deploymentPipeline,
|
||||
cancellationToken);
|
||||
}
|
||||
finally
|
||||
@@ -964,9 +988,15 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="progressReporter"><see cref="JobProgressReporter"/> of the operation.</param>
|
||||
/// <param name="username">The username for the <see cref="credentialsProvider"/>.</param>
|
||||
/// <param name="password">The password for the <see cref="credentialsProvider"/>.</param>
|
||||
/// <param name="deploymentPipeline">If any events created should be marked as part of the deployment pipeline.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
async Task UpdateSubmodules(JobProgressReporter progressReporter, string username, string password, CancellationToken cancellationToken)
|
||||
async Task UpdateSubmodules(
|
||||
JobProgressReporter progressReporter,
|
||||
string username,
|
||||
string password,
|
||||
bool deploymentPipeline,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var submoduleCount = libGitRepo.Submodules.Count();
|
||||
if (submoduleCount == 0)
|
||||
@@ -1032,7 +1062,11 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
}
|
||||
}
|
||||
|
||||
await eventConsumer.HandleEvent(EventType.RepoSubmoduleUpdate, new List<string> { submodule.Name }, cancellationToken);
|
||||
await eventConsumer.HandleEvent(
|
||||
EventType.RepoSubmoduleUpdate,
|
||||
new List<string> { submodule.Name },
|
||||
deploymentPipeline,
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,11 +152,10 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
CancellationToken cancellationToken)
|
||||
#pragma warning restore CA1502, CA1506
|
||||
{
|
||||
var repoManager = instance.RepositoryManager;
|
||||
using var repo = await repoManager.LoadRepository(cancellationToken);
|
||||
if (repo == null)
|
||||
throw new JobException(ErrorCode.RepoMissing);
|
||||
_ = job; // shuts up an IDE warning
|
||||
|
||||
var repoManager = instance.RepositoryManager;
|
||||
using var repo = await repoManager.LoadRepository(cancellationToken) ?? throw new JobException(ErrorCode.RepoMissing);
|
||||
var modelHasShaOrReference = model.CheckoutSha != null || model.Reference != null;
|
||||
|
||||
var startReference = repo.Reference;
|
||||
@@ -252,11 +251,21 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
if (!repo.Tracking)
|
||||
throw new JobException(ErrorCode.RepoReferenceRequired);
|
||||
await repo.FetchOrigin(currentModel.AccessUser, currentModel.AccessToken, NextProgressReporter("Fetch Origin"), cancellationToken);
|
||||
await repo.FetchOrigin(
|
||||
NextProgressReporter("Fetch Origin"),
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
false,
|
||||
cancellationToken);
|
||||
|
||||
if (!modelHasShaOrReference)
|
||||
{
|
||||
var fastForward = await repo.MergeOrigin(committerName, currentModel.CommitterEmail, NextProgressReporter("Merge Origin"), cancellationToken);
|
||||
var fastForward = await repo.MergeOrigin(
|
||||
NextProgressReporter("Merge Origin"),
|
||||
committerName,
|
||||
currentModel.CommitterEmail,
|
||||
false,
|
||||
cancellationToken);
|
||||
if (!fastForward.HasValue)
|
||||
throw new JobException(ErrorCode.RepoMergeConflict);
|
||||
lastRevisionInfo.OriginCommitSha = await repo.GetOriginSha(cancellationToken);
|
||||
@@ -264,12 +273,13 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
if (fastForward.Value)
|
||||
{
|
||||
await repo.Sychronize(
|
||||
NextProgressReporter("Sychronize"),
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
currentModel.CommitterName,
|
||||
currentModel.CommitterEmail,
|
||||
NextProgressReporter("Sychronize"),
|
||||
true,
|
||||
false,
|
||||
cancellationToken);
|
||||
postUpdateSha = repo.Head;
|
||||
}
|
||||
@@ -315,18 +325,20 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
if (!repo.Tracking)
|
||||
throw new JobException(ErrorCode.RepoReferenceNotTracking);
|
||||
await repo.ResetToOrigin(
|
||||
NextProgressReporter("Reset to Origin"),
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
updateSubmodules,
|
||||
NextProgressReporter("Reset to Origin"),
|
||||
false,
|
||||
cancellationToken);
|
||||
await repo.Sychronize(
|
||||
NextProgressReporter("Synchronize"),
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
currentModel.CommitterName,
|
||||
currentModel.CommitterEmail,
|
||||
NextProgressReporter("Synchronize"),
|
||||
true,
|
||||
false,
|
||||
cancellationToken);
|
||||
await CallLoadRevInfo();
|
||||
|
||||
@@ -473,7 +485,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
if (revInfoWereLookingFor != null)
|
||||
{
|
||||
// goteem
|
||||
logger.LogDebug("Reusing existing SHA {0}...", revInfoWereLookingFor.CommitSha);
|
||||
logger.LogDebug("Reusing existing SHA {sha}...", revInfoWereLookingFor.CommitSha);
|
||||
await repo.ResetToSha(revInfoWereLookingFor.CommitSha, NextProgressReporter($"Reset to {revInfoWereLookingFor.CommitSha[..7]}"), cancellationToken);
|
||||
lastRevisionInfo = revInfoWereLookingFor;
|
||||
}
|
||||
@@ -535,11 +547,12 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
if (currentModel.PushTestMergeCommits.Value && (startSha != currentHead || (postUpdateSha != null && postUpdateSha != currentHead)))
|
||||
{
|
||||
await repo.Sychronize(
|
||||
NextProgressReporter("Synchronize"),
|
||||
currentModel.AccessUser,
|
||||
currentModel.AccessToken,
|
||||
currentModel.CommitterName,
|
||||
currentModel.CommitterEmail,
|
||||
NextProgressReporter("Synchronize"),
|
||||
false,
|
||||
false,
|
||||
cancellationToken);
|
||||
await UpdateRevInfo();
|
||||
|
||||
@@ -13,15 +13,14 @@ using Newtonsoft.Json;
|
||||
|
||||
using Serilog.Context;
|
||||
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Common.Extensions;
|
||||
using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
using Tgstation.Server.Host.Components.Interop;
|
||||
using Tgstation.Server.Host.Components.Interop.Bridge;
|
||||
using Tgstation.Server.Host.Components.Interop.Topic;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.System;
|
||||
using Tgstation.Server.Host.Utils;
|
||||
|
||||
@@ -82,17 +81,15 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
get => rebootGate;
|
||||
set
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
Task toAwait = null;
|
||||
var tcs = new TaskCompletionSource<Task>();
|
||||
async Task Wrap()
|
||||
{
|
||||
await tcs.Task;
|
||||
var toAwait = await tcs.Task;
|
||||
await toAwait;
|
||||
await value;
|
||||
}
|
||||
|
||||
toAwait = Interlocked.Exchange(ref rebootGate, Wrap());
|
||||
tcs.SetResult();
|
||||
tcs.SetResult(Interlocked.Exchange(ref rebootGate, Wrap()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -779,7 +776,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
}
|
||||
|
||||
Interlocked.Exchange(ref rebootTcs, new TaskCompletionSource()).SetResult();
|
||||
await RebootGate.WithToken(cancellationToken);
|
||||
await RebootGate.WaitAsync(cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -10,9 +10,9 @@ using Byond.TopicSender;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Common.Extensions;
|
||||
using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Deployment;
|
||||
@@ -552,6 +552,7 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
{
|
||||
process.Id.ToString(CultureInfo.InvariantCulture),
|
||||
},
|
||||
false,
|
||||
cancellationToken);
|
||||
|
||||
return process;
|
||||
|
||||
@@ -128,6 +128,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SessionConfiguration"/> for <see cref="Configuration"/>.
|
||||
/// </summary>
|
||||
readonly SessionConfiguration sessionConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SemaphoreSlim"/> for <see cref="Configuration"/>. Also used as a <see langword="lock"/> <see cref="object"/>.
|
||||
/// </summary>
|
||||
@@ -155,6 +160,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
/// <param name="fileTransferService">The value of <see cref="fileTransferService"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
/// <param name="generalConfiguration">The value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="sessionConfiguration">The value of <see cref="sessionConfiguration"/>.</param>
|
||||
public Configuration(
|
||||
IIOManager ioManager,
|
||||
ISynchronousIOManager synchronousIOManager,
|
||||
@@ -164,7 +170,8 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
IFileTransferTicketProvider fileTransferService,
|
||||
ILogger<Configuration> logger,
|
||||
GeneralConfiguration generalConfiguration)
|
||||
GeneralConfiguration generalConfiguration,
|
||||
SessionConfiguration sessionConfiguration)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager));
|
||||
@@ -175,6 +182,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.generalConfiguration = generalConfiguration ?? throw new ArgumentNullException(nameof(generalConfiguration));
|
||||
this.sessionConfiguration = sessionConfiguration ?? throw new ArgumentNullException(nameof(sessionConfiguration));
|
||||
|
||||
semaphore = new SemaphoreSlim(1);
|
||||
disposeCts = new CancellationTokenSource();
|
||||
@@ -592,7 +600,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
public Task StopAsync(CancellationToken cancellationToken) => EnsureDirectories(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task HandleEvent(EventType eventType, IEnumerable<string> parameters, CancellationToken cancellationToken)
|
||||
public async Task HandleEvent(EventType eventType, IEnumerable<string> parameters, bool deploymentPipeline, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(parameters);
|
||||
|
||||
@@ -639,6 +647,9 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
noShellExecute: true))
|
||||
using (cancellationToken.Register(() => script.Terminate()))
|
||||
{
|
||||
if (sessionConfiguration.LowPriorityDeploymentProcesses)
|
||||
script.AdjustPriority(false);
|
||||
|
||||
var exitCode = await script.Lifetime;
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var scriptOutput = await script.GetCombinedOutput(cancellationToken);
|
||||
|
||||
@@ -112,7 +112,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
var eventType = Server.TerminationWasRequested
|
||||
? EventType.WorldEndProcess
|
||||
: EventType.WatchdogCrash;
|
||||
await HandleEvent(eventType, Enumerable.Empty<string>(), false, cancellationToken);
|
||||
await HandleEventImpl(eventType, Enumerable.Empty<string>(), false, cancellationToken);
|
||||
|
||||
var exitWord = Server.TerminationWasRequested ? "exited" : "crashed";
|
||||
if (Server.RebootState == Session.RebootState.Shutdown)
|
||||
@@ -143,7 +143,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
gracefulRebootRequired = false;
|
||||
Server.ResetRebootState();
|
||||
|
||||
var eventTask = HandleEvent(EventType.WorldReboot, Enumerable.Empty<string>(), false, cancellationToken);
|
||||
var eventTask = HandleEventImpl(EventType.WorldReboot, Enumerable.Empty<string>(), false, cancellationToken);
|
||||
try
|
||||
{
|
||||
switch (rebootState)
|
||||
@@ -174,7 +174,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
await HandleNewDmbAvailable(cancellationToken);
|
||||
break;
|
||||
case MonitorActivationReason.ActiveServerPrimed:
|
||||
await HandleEvent(EventType.WorldPrime, Enumerable.Empty<string>(), false, cancellationToken);
|
||||
await HandleEventImpl(EventType.WorldPrime, Enumerable.Empty<string>(), false, cancellationToken);
|
||||
break;
|
||||
case MonitorActivationReason.ActiveServerStartup:
|
||||
break; // unused in BasicWatchdog
|
||||
|
||||
@@ -411,7 +411,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
if (Status != WatchdogStatus.Offline)
|
||||
{
|
||||
Logger.LogDebug("Waiting for server to gracefully shut down.");
|
||||
await monitorTask.WithToken(cancellationToken);
|
||||
await monitorTask.WaitAsync(cancellationToken);
|
||||
}
|
||||
else
|
||||
Logger.LogTrace("Graceful shutdown requested but server is already offline.");
|
||||
@@ -449,7 +449,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
async Task IEventConsumer.HandleEvent(EventType eventType, IEnumerable<string> parameters, CancellationToken cancellationToken)
|
||||
async Task IEventConsumer.HandleEvent(EventType eventType, IEnumerable<string> parameters, bool deploymentPipeline, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(parameters);
|
||||
|
||||
@@ -509,7 +509,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
? "Launching..."
|
||||
: "Reattaching..."); // simple announce
|
||||
if (reattachInfo == null)
|
||||
eventTask = HandleEvent(EventType.WatchdogLaunch, Enumerable.Empty<string>(), false, cancellationToken);
|
||||
eventTask = HandleEventImpl(EventType.WatchdogLaunch, Enumerable.Empty<string>(), false, cancellationToken);
|
||||
}
|
||||
|
||||
// since neither server is running, this is safe to do
|
||||
@@ -589,7 +589,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
protected async Task CheckLaunchResult(ISessionController controller, string serverName, CancellationToken cancellationToken)
|
||||
{
|
||||
var launchResult = await controller.LaunchResult.WithToken(cancellationToken);
|
||||
var launchResult = await controller.LaunchResult.WaitAsync(cancellationToken);
|
||||
|
||||
// Dead sessions won't trigger this
|
||||
if (launchResult.ExitCode.HasValue) // you killed us ray...
|
||||
@@ -693,13 +693,13 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <param name="relayToSession">If the event should be sent to DreamDaemon.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
protected async Task HandleEvent(EventType eventType, IEnumerable<string> parameters, bool relayToSession, CancellationToken cancellationToken)
|
||||
protected async Task HandleEventImpl(EventType eventType, IEnumerable<string> parameters, bool relayToSession, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sessionEventTask = relayToSession ? ((IEventConsumer)this).HandleEvent(eventType, parameters, cancellationToken) : Task.CompletedTask;
|
||||
var sessionEventTask = relayToSession ? ((IEventConsumer)this).HandleEvent(eventType, parameters, false, cancellationToken) : Task.CompletedTask;
|
||||
await Task.WhenAll(
|
||||
eventConsumer.HandleEvent(eventType, parameters, cancellationToken),
|
||||
eventConsumer.HandleEvent(eventType, parameters, false, cancellationToken),
|
||||
sessionEventTask);
|
||||
}
|
||||
catch (JobException ex)
|
||||
@@ -872,7 +872,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
serverPrimed);
|
||||
|
||||
// wait for something to happen
|
||||
await toWaitOn.WithToken(cancellationToken);
|
||||
await toWaitOn.WaitAsync(cancellationToken);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Logger.LogTrace("Monitor activated");
|
||||
@@ -1005,7 +1005,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
return;
|
||||
if (!graceful)
|
||||
{
|
||||
var eventTask = HandleEvent(
|
||||
var eventTask = HandleEventImpl(
|
||||
releaseServers
|
||||
? EventType.WatchdogDetach
|
||||
: EventType.WatchdogShutdown,
|
||||
|
||||
@@ -34,11 +34,6 @@ namespace Tgstation.Server.Host.Configuration
|
||||
/// </summary>
|
||||
public bool DropDatabase { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to indicate that the database is being loaded to generate migrations. Should not be used in production!.
|
||||
/// </summary>
|
||||
public bool DesignTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="string"/> form of the <see cref="global::System.Version"/> of the target server.
|
||||
/// </summary>
|
||||
|
||||
@@ -10,21 +10,6 @@
|
||||
/// </summary>
|
||||
public const string Section = "Elasticsearch";
|
||||
|
||||
/// <summary>
|
||||
/// Default value of <see cref="Host"/>.
|
||||
/// </summary>
|
||||
const string DefaultHost = "http://127.0.0.1:9200"; // localhost
|
||||
|
||||
/// <summary>
|
||||
/// Default value of <see cref="Username"/>.
|
||||
/// </summary>
|
||||
const string DefaultUsername = "my_username";
|
||||
|
||||
/// <summary>
|
||||
/// Default value of <see cref="Password"/>.
|
||||
/// </summary>
|
||||
const string DefaultPassword = "my_password";
|
||||
|
||||
/// <summary>
|
||||
/// Do we want to enable elasticsearch or not?.
|
||||
/// </summary>
|
||||
@@ -33,16 +18,16 @@
|
||||
/// <summary>
|
||||
/// The host of the elasticsearch endpoint.
|
||||
/// </summary>
|
||||
public string Host { get; set; } = DefaultHost;
|
||||
public string Host { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Username for elasticsearch.
|
||||
/// </summary>
|
||||
public string Username { get; set; } = DefaultUsername;
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Password for elasticsearch.
|
||||
/// </summary>
|
||||
public string Password { get; set; } = DefaultPassword;
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ namespace Tgstation.Server.Host.Configuration
|
||||
? Directory
|
||||
: ioManager.ConcatPath(
|
||||
directoryToUse,
|
||||
assemblyInformationProvider.VersionPrefix);
|
||||
assemblyInformationProvider.VersionPrefix,
|
||||
"logs");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Tgstation.Server.Host.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Unstable configuration options used internally by TGS.
|
||||
/// </summary>
|
||||
public sealed class InternalConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// The key for the <see cref="Microsoft.Extensions.Configuration.IConfigurationSection"/> the <see cref="InternalConfiguration"/> resides in.
|
||||
/// </summary>
|
||||
public const string Section = "Internal";
|
||||
|
||||
/// <summary>
|
||||
/// The name of the pipe opened by the host watchdog, if any.
|
||||
/// </summary>
|
||||
public string CommandPipe { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the server is running under SystemD.
|
||||
/// </summary>
|
||||
public bool UsingSystemD { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The base path for the app settings configuration files.
|
||||
/// </summary>
|
||||
public string AppSettingsBasePath { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -41,9 +41,9 @@ namespace Tgstation.Server.Host.Controllers
|
||||
const string OctokitException = "Bad GitHub API response, check configuration!";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IGitHubService"/> for the <see cref="AdministrationController"/>.
|
||||
/// The <see cref="IGitHubServiceFactory"/> for the <see cref="AdministrationController"/>.
|
||||
/// </summary>
|
||||
readonly IGitHubService gitHubService;
|
||||
readonly IGitHubServiceFactory gitHubServiceFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IServerControl"/> for the <see cref="AdministrationController"/>.
|
||||
@@ -85,7 +85,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
/// </summary>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/>.</param>
|
||||
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/>.</param>
|
||||
/// <param name="gitHubService">The value of <see cref="gitHubService"/>.</param>
|
||||
/// <param name="gitHubServiceFactory">The value of <see cref="gitHubServiceFactory"/>.</param>
|
||||
/// <param name="serverControl">The value of <see cref="serverControl"/>.</param>
|
||||
/// <param name="serverUpdateInitiator">The value of <see cref="serverUpdateInitiator"/>.</param>
|
||||
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
|
||||
@@ -97,7 +97,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
public AdministrationController(
|
||||
IDatabaseContext databaseContext,
|
||||
IAuthenticationContextFactory authenticationContextFactory,
|
||||
IGitHubService gitHubService,
|
||||
IGitHubServiceFactory gitHubServiceFactory,
|
||||
IServerControl serverControl,
|
||||
IServerUpdateInitiator serverUpdateInitiator,
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
@@ -112,7 +112,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
logger,
|
||||
true)
|
||||
{
|
||||
this.gitHubService = gitHubService ?? throw new ArgumentNullException(nameof(gitHubService));
|
||||
this.gitHubServiceFactory = gitHubServiceFactory ?? throw new ArgumentNullException(nameof(gitHubServiceFactory));
|
||||
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
this.serverUpdateInitiator = serverUpdateInitiator ?? throw new ArgumentNullException(nameof(serverUpdateInitiator));
|
||||
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
|
||||
@@ -143,6 +143,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
Uri repoUrl = null;
|
||||
try
|
||||
{
|
||||
var gitHubService = gitHubServiceFactory.CreateService();
|
||||
var repositoryUrlTask = gitHubService.GetUpdatesRepositoryUrl(cancellationToken);
|
||||
var releases = await gitHubService.GetTgsReleases(cancellationToken);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ using Serilog.Context;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Response;
|
||||
using Tgstation.Server.Common.Extensions;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user