mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-22 12:37:24 +01:00
Merge pull request #1572 from tgstation/1569-Vdproj
Installer `.exe` + `winget` package + `.exe` signing
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
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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'))))"
|
||||
@@ -219,11 +239,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
|
||||
@@ -259,11 +282,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
|
||||
@@ -379,6 +405,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
|
||||
@@ -542,14 +585,17 @@ 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: Run Live Tests
|
||||
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
|
||||
@@ -563,18 +609,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
|
||||
rm ../../Artifacts/Console/Tgstation.Server.Host.Console
|
||||
|
||||
- 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' }}
|
||||
@@ -875,30 +918,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
|
||||
@@ -911,6 +955,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_*
|
||||
|
||||
@@ -920,11 +990,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
|
||||
@@ -1066,8 +1311,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
|
||||
@@ -1083,6 +1334,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: |
|
||||
@@ -1120,6 +1401,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: |
|
||||
@@ -1130,7 +1433,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
|
||||
@@ -1203,6 +1506,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
|
||||
@@ -1244,4 +1557,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,7 +18,25 @@ Older server versions can be found in the V# branches of this repository. Note t
|
||||
|
||||
### Pre-Requisites
|
||||
|
||||
- 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
|
||||
|
||||
@@ -26,17 +44,60 @@ Follow the instructions for your OS below.
|
||||
|
||||
#### Windows
|
||||
|
||||
Download and install the [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 (Most modern systems have it by default. 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), you can test this by opening a command prompt and running `dotnet --list-runtimes`.
|
||||
###### Note about Digital Signatures
|
||||
|
||||
[Download the latest release .zip](https://github.com/tgstation/tgstation-server/releases/latest). You probably want the `ServerService` package. Choose `ServerConsole` if you prefer not to use the Windows service.
|
||||
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.
|
||||
|
||||
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.
|
||||
##### winget (Windows 10 or later)
|
||||
|
||||
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.
|
||||
[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
|
||||
|
||||
|
||||
+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
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
install:
|
||||
build/package/deb/install_artifacts.sh "$(DESTDIR)"
|
||||
install -D build/package/deb/appsettings.Initial.yml "$(DESTDIR)/etc/tgstation-server/appsettings.Production.yml"
|
||||
install src/Tgstation.Server.Host/appsettings.yml "$(DESTDIR)/etc/tgstation-server/appsettings.ex.yml"
|
||||
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"
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# tgstation-server configuration file
|
||||
# See /opt/tgstation-server/appsettings.yml for details on individual configuration options
|
||||
@@ -21,8 +21,8 @@ Depends:
|
||||
libstdc++6:i386 [amd64],
|
||||
libstdc++6 [i386],
|
||||
gcc-multilib [amd64],
|
||||
libsystemd0,
|
||||
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 +0,0 @@
|
||||
/etc/tgstation-server/appsettings.Production.yml /opt/tgstation-server/appsettings.Production.yml
|
||||
@@ -7,15 +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/Tgstation.Server.Host.Console
|
||||
rm artifacts/lib/Default/appsettings.yml
|
||||
|
||||
override_dh_auto_install:
|
||||
cp build/package/deb/MakeInstall ./Makefile
|
||||
|
||||
@@ -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 ../../../..
|
||||
}
|
||||
@@ -10,15 +10,13 @@ After=mssql-server.service
|
||||
Type=notify-reload
|
||||
NotifyAccess=all
|
||||
WorkingDirectory=/opt/tgstation-server
|
||||
ExecStart=/usr/bin/dotnet Tgstation.Server.Host.Console.dll --General:SetupWizardMode=Never
|
||||
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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
+20
-9
@@ -7,22 +7,33 @@ using Microsoft.Extensions.Logging;
|
||||
using Mono.Unix;
|
||||
using Mono.Unix.Native;
|
||||
|
||||
namespace Tgstation.Server.Host.Watchdog
|
||||
using Tgstation.Server.Host.Watchdog;
|
||||
|
||||
namespace Tgstation.Server.Host.Console
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper for checking POSIX signals.
|
||||
/// <see cref="ISignalChecker"/> for checking POSIX signals.
|
||||
/// </summary>
|
||||
static class SignalChecker
|
||||
sealed class PosixSignalChecker : ISignalChecker
|
||||
{
|
||||
/// <summary>
|
||||
/// Forwards certain signals to a given <paramref name="childPid"/>.
|
||||
/// The <see cref="ILogger"/> for the <see cref="PosixSignalChecker"/>.
|
||||
/// </summary>
|
||||
/// <param name="logger">The <see cref="ILogger"/> to write to.</param>
|
||||
/// <param name="childPid">The <see cref="System.Diagnostics.Process.Id"/> of the process to forward signals to.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
public static async Task CheckSignals(ILogger logger, int childPid, CancellationToken cancellationToken)
|
||||
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)
|
||||
{
|
||||
@@ -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,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,7 +10,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Tgstation.Server.Common.Extensions;
|
||||
using Tgstation.Server.Host.Common;
|
||||
|
||||
namespace Tgstation.Server.Host.Watchdog
|
||||
@@ -19,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>
|
||||
@@ -27,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;
|
||||
@@ -52,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);
|
||||
@@ -74,10 +81,10 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
|
||||
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);
|
||||
@@ -90,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"))
|
||||
@@ -143,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)))
|
||||
@@ -176,12 +190,9 @@ namespace Tgstation.Server.Host.Watchdog
|
||||
}
|
||||
}))
|
||||
{
|
||||
var processTask = tcs.Task;
|
||||
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
var checkerTask = isWindows
|
||||
? Task.CompletedTask
|
||||
: SignalChecker.CheckSignals(logger, childPid, cts.Token);
|
||||
var checkerTask = signalChecker.CheckSignals(StartProcess, cts.Token);
|
||||
try
|
||||
{
|
||||
await processTask;
|
||||
@@ -228,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
|
||||
@@ -331,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
|
||||
@@ -355,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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,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);
|
||||
@@ -302,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);
|
||||
@@ -453,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;
|
||||
}
|
||||
|
||||
@@ -616,8 +618,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);
|
||||
|
||||
@@ -633,9 +642,7 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
trustedFileText = $"{trustedFileText.Trim()}{Environment.NewLine}";
|
||||
}
|
||||
else
|
||||
{
|
||||
trustedFileText = String.Empty;
|
||||
}
|
||||
|
||||
if (trustedFileText.Contains(fullDmbPath, StringComparison.Ordinal))
|
||||
return;
|
||||
|
||||
@@ -94,7 +94,11 @@ namespace Tgstation.Server.Host.Components.Byond
|
||||
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -806,10 +806,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();
|
||||
|
||||
+2
-3
@@ -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);
|
||||
@@ -140,7 +139,7 @@ namespace Tgstation.Server.Host.Components.Deployment.Remote
|
||||
$"{remoteRepositoryOwner}/{remoteRepositoryName}",
|
||||
testMergeNumber,
|
||||
new CreateMergeRequestNoteRequest(comment))
|
||||
.WithToken(cancellationToken);
|
||||
.WaitAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.");
|
||||
@@ -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...
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,6 +6,8 @@ using System.Linq;
|
||||
|
||||
using Cyberboss.AspNetCore.AsyncInitializer;
|
||||
|
||||
using Elastic.CommonSchema.Serilog;
|
||||
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||
@@ -23,6 +25,7 @@ using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Serilog.Formatting.Display;
|
||||
using Serilog.Sinks.Elasticsearch;
|
||||
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Common.Http;
|
||||
@@ -154,6 +157,7 @@ namespace Tgstation.Server.Host.Core
|
||||
};
|
||||
|
||||
var microsoftEventLevel = ConvertSeriLogLevel(postSetupServices.FileLoggingConfiguration.MicrosoftLogLevel);
|
||||
var elasticsearchConfiguration = postSetupServices.ElasticsearchConfiguration;
|
||||
services.SetupLogging(
|
||||
config =>
|
||||
{
|
||||
@@ -191,7 +195,30 @@ namespace Tgstation.Server.Host.Core
|
||||
rollingInterval: RollingInterval.Day,
|
||||
rollOnFileSizeLimit: true);
|
||||
},
|
||||
postSetupServices.ElasticsearchConfiguration);
|
||||
elasticsearchConfiguration.Enable
|
||||
? new ElasticsearchSinkOptions(
|
||||
new Uri(
|
||||
String.IsNullOrWhiteSpace(elasticsearchConfiguration.Host)
|
||||
? throw new InvalidOperationException($"Missing {ElasticsearchConfiguration.Section}:{nameof(elasticsearchConfiguration.Host)}!")
|
||||
: elasticsearchConfiguration.Host))
|
||||
{
|
||||
// Yes I know this means they cannot use a self signed cert unless they also have authentication, but lets be real here
|
||||
// No one is going to be doing one of those but not the other
|
||||
ModifyConnectionSettings = connectionConfigration => (!String.IsNullOrWhiteSpace(elasticsearchConfiguration.Username) && !String.IsNullOrWhiteSpace(elasticsearchConfiguration.Password))
|
||||
? connectionConfigration
|
||||
.BasicAuthentication(
|
||||
elasticsearchConfiguration.Username,
|
||||
elasticsearchConfiguration.Password)
|
||||
.ServerCertificateValidationCallback((o, certificate, chain, errors) => true)
|
||||
: null,
|
||||
CustomFormatter = new EcsTextFormatter(),
|
||||
AutoRegisterTemplate = true,
|
||||
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
|
||||
IndexFormat = "tgs-logs",
|
||||
}
|
||||
: null,
|
||||
postSetupServices.InternalConfiguration,
|
||||
postSetupServices.FileLoggingConfiguration);
|
||||
|
||||
// configure bearer token validation
|
||||
services
|
||||
@@ -335,12 +362,12 @@ namespace Tgstation.Server.Host.Core
|
||||
services.AddSingleton(x => new Lazy<IProcessExecutor>(() => x.GetRequiredService<IProcessExecutor>(), true));
|
||||
services.AddSingleton<INetworkPromptReaper, PosixNetworkPromptReaper>();
|
||||
|
||||
services.AddSingleton<IHostedService, PosixSignalHandler>();
|
||||
|
||||
services.AddSingleton<SystemDManager>();
|
||||
services.AddSingleton<IHostedService>(x => x.GetRequiredService<SystemDManager>());
|
||||
services.AddHostedService<PosixSignalHandler>();
|
||||
}
|
||||
|
||||
if (postSetupServices.InternalConfiguration.UsingSystemD)
|
||||
services.AddHostedService<SystemDManager>();
|
||||
|
||||
// configure file transfer services
|
||||
services.AddSingleton<FileTransferService>();
|
||||
services.AddSingleton<IFileTransferStreamHandler>(x => x.GetRequiredService<FileTransferService>());
|
||||
@@ -368,10 +395,11 @@ namespace Tgstation.Server.Host.Core
|
||||
// configure misc services
|
||||
services.AddSingleton<IProcessExecutor, ProcessExecutor>();
|
||||
services.AddSingleton<ISynchronousIOManager, SynchronousIOManager>();
|
||||
services.AddFileDownloader();
|
||||
services.AddSingleton<IServerPortProvider, ServerPortProivder>();
|
||||
services.AddSingleton<ITopicClientFactory, TopicClientFactory>();
|
||||
services.AddHostedService<CommandPipeReader>();
|
||||
|
||||
services.AddFileDownloader();
|
||||
services.AddGitHub();
|
||||
|
||||
// configure root services
|
||||
@@ -438,7 +466,7 @@ namespace Tgstation.Server.Host.Core
|
||||
|
||||
// 503 requests made while the application is starting
|
||||
applicationBuilder.UseAsyncInitialization<IInstanceManager>(
|
||||
(instanceManager, cancellationToken) => instanceManager.Ready.WithToken(cancellationToken));
|
||||
(instanceManager, cancellationToken) => instanceManager.Ready.WaitAsync(cancellationToken));
|
||||
|
||||
if (generalConfiguration.HostApiDocumentation)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using Tgstation.Server.Host.Common;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Reads from the command pipe opened by the host watchdog.
|
||||
/// </summary>
|
||||
sealed class CommandPipeReader : BackgroundService
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IServerControl"/> for the <see cref="CommandPipeReader"/>.
|
||||
/// </summary>
|
||||
readonly IServerControl serverControl;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="CommandPipeReader"/>.
|
||||
/// </summary>
|
||||
readonly ILogger<CommandPipeReader> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InternalConfiguration"/> for the <see cref="CommandPipeReader"/>.
|
||||
/// </summary>
|
||||
readonly InternalConfiguration internalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommandPipeReader"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serverControl">The value of <see cref="serverControl"/>.</param>
|
||||
/// <param name="internalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="internalConfiguration"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
public CommandPipeReader(
|
||||
IServerControl serverControl,
|
||||
IOptions<InternalConfiguration> internalConfigurationOptions,
|
||||
ILogger<CommandPipeReader> logger)
|
||||
{
|
||||
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
internalConfiguration = internalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(internalConfigurationOptions));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("Starting...");
|
||||
|
||||
var pipeName = internalConfiguration.CommandPipe;
|
||||
if (string.IsNullOrWhiteSpace(pipeName))
|
||||
{
|
||||
logger.LogDebug("No command pipe name specified in configuration");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var pipeClient = new AnonymousPipeClientStream(PipeDirection.In, pipeName);
|
||||
using var streamReader = new StreamReader(pipeClient, leaveOpen: true);
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
logger.LogTrace("Waiting to read command line...");
|
||||
var line = await streamReader.ReadLineAsync().WaitAsync(cancellationToken);
|
||||
|
||||
logger?.LogInformation("Received pipe command: {command}", line);
|
||||
switch (line)
|
||||
{
|
||||
case PipeCommands.CommandStop:
|
||||
await serverControl.Die(null);
|
||||
break;
|
||||
case PipeCommands.CommandGracefulShutdown:
|
||||
await serverControl.GracefulShutdown(false);
|
||||
break;
|
||||
case PipeCommands.CommandDetachingShutdown:
|
||||
await serverControl.GracefulShutdown(true);
|
||||
break;
|
||||
case null:
|
||||
logger.LogError("Read null from pipe!");
|
||||
return;
|
||||
default:
|
||||
logger?.LogWarning("Unrecognized pipe command: {command}", line);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
logger?.LogTrace(ex, "Command read task cancelled!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger?.LogError(ex, "Command read task errored!");
|
||||
}
|
||||
finally
|
||||
{
|
||||
logger?.LogTrace("Command read task exiting...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,9 @@ namespace Tgstation.Server.Host.Core
|
||||
sealed class ServerUpdater : IServerUpdater, IServerUpdateExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IGitHubService"/> for the <see cref="ServerUpdater"/>.
|
||||
/// The <see cref="IGitHubServiceFactory"/> for the <see cref="ServerUpdater"/>.
|
||||
/// </summary>
|
||||
readonly IGitHubService gitHubService;
|
||||
readonly IGitHubServiceFactory gitHubServiceFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IIOManager"/> for the <see cref="ServerUpdater"/>.
|
||||
@@ -64,7 +64,7 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerUpdater"/> class.
|
||||
/// </summary>
|
||||
/// <param name="gitHubService">The value of <see cref="gitHubService"/>.</param>
|
||||
/// <param name="gitHubServiceFactory">The value of <see cref="gitHubServiceFactory"/>.</param>
|
||||
/// <param name="ioManager">The value of <see cref="ioManager"/>.</param>
|
||||
/// <param name="fileDownloader">The value of <see cref="fileDownloader"/>.</param>
|
||||
/// <param name="serverControl">The value of <see cref="serverControl"/>.</param>
|
||||
@@ -72,7 +72,7 @@ namespace Tgstation.Server.Host.Core
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="updatesConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="updatesConfiguration"/>.</param>
|
||||
public ServerUpdater(
|
||||
IGitHubService gitHubService,
|
||||
IGitHubServiceFactory gitHubServiceFactory,
|
||||
IIOManager ioManager,
|
||||
IFileDownloader fileDownloader,
|
||||
IServerControl serverControl,
|
||||
@@ -80,7 +80,7 @@ namespace Tgstation.Server.Host.Core
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptions<UpdatesConfiguration> updatesConfigurationOptions)
|
||||
{
|
||||
this.gitHubService = gitHubService ?? throw new ArgumentNullException(nameof(gitHubService));
|
||||
this.gitHubServiceFactory = gitHubServiceFactory ?? throw new ArgumentNullException(nameof(gitHubServiceFactory));
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.fileDownloader = fileDownloader ?? throw new ArgumentNullException(nameof(fileDownloader));
|
||||
this.serverControl = serverControl ?? throw new ArgumentNullException(nameof(serverControl));
|
||||
@@ -288,6 +288,7 @@ namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
logger.LogDebug("Looking for GitHub releases version {version}...", newVersion);
|
||||
|
||||
var gitHubService = gitHubServiceFactory.CreateService();
|
||||
var releases = await gitHubService.GetTgsReleases(cancellationToken);
|
||||
foreach (var kvp in releases)
|
||||
{
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace Tgstation.Server.Host.Database.Design
|
||||
{
|
||||
var dbConfig = new DatabaseConfiguration
|
||||
{
|
||||
DesignTime = true,
|
||||
DatabaseType = databaseType,
|
||||
ConnectionString = connectionString,
|
||||
ServerVersion = serverVersion,
|
||||
|
||||
@@ -12,11 +12,11 @@ namespace Tgstation.Server.Host.Database.Design
|
||||
/// <inheritdoc />
|
||||
public SqliteDatabaseContext CreateDbContext(string[] args)
|
||||
{
|
||||
SqliteDatabaseContext.DesignTime = true;
|
||||
return new SqliteDatabaseContext(
|
||||
DesignTimeDbContextFactoryHelpers.CreateDatabaseContextOptions<SqliteDatabaseContext>(
|
||||
DatabaseType.Sqlite,
|
||||
"Data Source=tgs_design.sqlite3;Mode=ReadWriteCreate"));
|
||||
"Data Source=tgs_design.sqlite3;Mode=ReadWriteCreate"),
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,24 +14,41 @@ namespace Tgstation.Server.Host.Database
|
||||
sealed class SqliteDatabaseContext : DatabaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Static property to receive the configured value of <see cref="DatabaseConfiguration.DesignTime"/>.
|
||||
/// If the database context is running in design time mode.
|
||||
/// </summary>
|
||||
public static bool DesignTime { get; set; }
|
||||
readonly bool designTime;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SqliteDatabaseContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext"/>.</param>
|
||||
public SqliteDatabaseContext(DbContextOptions<SqliteDatabaseContext> dbContextOptions) : base(dbContextOptions)
|
||||
public SqliteDatabaseContext(
|
||||
DbContextOptions<SqliteDatabaseContext> dbContextOptions)
|
||||
: this(dbContextOptions, false)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SqliteDatabaseContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext"/>.</param>
|
||||
/// <param name="designTime">The value of <see cref="designTime"/>.</param>
|
||||
internal SqliteDatabaseContext(
|
||||
DbContextOptions<SqliteDatabaseContext> dbContextOptions,
|
||||
bool designTime)
|
||||
: base(dbContextOptions)
|
||||
{
|
||||
this.designTime = designTime;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the <see cref="SqliteDatabaseContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="DbContextOptionsBuilder"/> to configure.</param>
|
||||
/// <param name="databaseConfiguration">The <see cref="DatabaseConfiguration"/>.</param>
|
||||
public static void ConfigureWith(DbContextOptionsBuilder options, DatabaseConfiguration databaseConfiguration)
|
||||
public static void ConfigureWith(
|
||||
DbContextOptionsBuilder options,
|
||||
DatabaseConfiguration databaseConfiguration)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ArgumentNullException.ThrowIfNull(databaseConfiguration);
|
||||
@@ -39,7 +56,6 @@ namespace Tgstation.Server.Host.Database
|
||||
if (databaseConfiguration.DatabaseType != DatabaseType.Sqlite)
|
||||
throw new InvalidOperationException($"Invalid DatabaseType for {nameof(SqliteDatabaseContext)}!");
|
||||
|
||||
DesignTime = databaseConfiguration.DesignTime;
|
||||
options.UseSqlite(databaseConfiguration.ConnectionString, sqliteOptions => sqliteOptions.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery));
|
||||
}
|
||||
|
||||
@@ -56,7 +72,7 @@ namespace Tgstation.Server.Host.Database
|
||||
// use the DateTimeOffsetToBinaryConverter
|
||||
// Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754
|
||||
// This only supports millisecond precision, but should be sufficient for most use cases.
|
||||
if (!DesignTime)
|
||||
if (!designTime)
|
||||
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
var properties = entityType
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Common.Extensions;
|
||||
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.Core.Events;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
using Elastic.CommonSchema.Serilog;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
@@ -105,13 +104,12 @@ namespace Tgstation.Server.Host.Extensions
|
||||
|
||||
serviceCollection.AddSingleton<IGitHubClientFactory, GitHubClientFactory>();
|
||||
serviceCollection.AddSingleton(typeof(IGitHubServiceFactory), gitHubServiceFactoryType);
|
||||
serviceCollection.AddSingleton(x => x.GetRequiredService<IGitHubServiceFactory>().CreateService());
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an additional <see cref="ILoggerProvider"/> to <see cref="IServiceCollection"/>s that call <see cref="SetupLogging(IServiceCollection, Action{LoggerConfiguration}, Action{LoggerSinkConfiguration}, ElasticsearchConfiguration)"/>.
|
||||
/// Add an additional <see cref="ILoggerProvider"/> to <see cref="IServiceCollection"/>s that call <see cref="SetupLogging(IServiceCollection, Action{LoggerConfiguration}, Action{LoggerSinkConfiguration}, ElasticsearchSinkOptions, InternalConfiguration, FileLoggingConfiguration)"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TLoggerProvider">The <see cref="Type"/> of <see cref="ILoggerProvider"/> to add.</typeparam>
|
||||
public static void UseAdditionalLoggerProvider<TLoggerProvider>() where TLoggerProvider : class, ILoggerProvider
|
||||
@@ -165,14 +163,22 @@ namespace Tgstation.Server.Host.Extensions
|
||||
/// <param name="serviceCollection">The <see cref="IServiceCollection"/> to configure.</param>
|
||||
/// <param name="configurationAction">Additional configuration for a given <see cref="LoggerConfiguration"/>.</param>
|
||||
/// <param name="sinkConfigurationAction">Additional configuration for a given <see cref="LoggerSinkConfiguration"/>.</param>
|
||||
/// <param name="elasticsearchConfiguration">Configuration for a given <see cref="ElasticsearchConfiguration"/>.</param>
|
||||
/// <param name="elasticsearchSinkOptions">The <see cref="ElasticsearchSinkOptions"/> to use, if any.</param>
|
||||
/// <param name="internalConfiguration">The active <see cref="InternalConfiguration"/>, if any.</param>
|
||||
/// <param name="fileLoggingConfiguration">The active <see cref="FileLoggingConfiguration"/>, if any. Must be set if <paramref name="internalConfiguration"/> is passed in.</param>
|
||||
/// <returns>The updated <paramref name="serviceCollection"/>.</returns>
|
||||
public static IServiceCollection SetupLogging(
|
||||
this IServiceCollection serviceCollection,
|
||||
Action<LoggerConfiguration> configurationAction,
|
||||
Action<LoggerSinkConfiguration> sinkConfigurationAction = null,
|
||||
ElasticsearchConfiguration elasticsearchConfiguration = null)
|
||||
=> serviceCollection.AddLogging(builder =>
|
||||
ElasticsearchSinkOptions elasticsearchSinkOptions = null,
|
||||
InternalConfiguration internalConfiguration = null,
|
||||
FileLoggingConfiguration fileLoggingConfiguration = null)
|
||||
{
|
||||
if (internalConfiguration != null)
|
||||
ArgumentNullException.ThrowIfNull(fileLoggingConfiguration);
|
||||
|
||||
return serviceCollection.AddLogging(builder =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
|
||||
@@ -190,29 +196,14 @@ namespace Tgstation.Server.Host.Extensions
|
||||
var template = "[{Timestamp:HH:mm:ss}] {Level:w3}: {SourceContext:l} ("
|
||||
+ SerilogContextHelper.Template
|
||||
+ "){NewLine} {Message:lj}{NewLine}{Exception}";
|
||||
sinkConfiguration.Console(outputTemplate: template, formatProvider: CultureInfo.InvariantCulture);
|
||||
|
||||
if (!((internalConfiguration?.UsingSystemD ?? false) && !fileLoggingConfiguration.Disable))
|
||||
sinkConfiguration.Console(outputTemplate: template, formatProvider: CultureInfo.InvariantCulture);
|
||||
sinkConfigurationAction?.Invoke(sinkConfiguration);
|
||||
});
|
||||
|
||||
if (elasticsearchConfiguration != null)
|
||||
{
|
||||
if (elasticsearchConfiguration.Enable)
|
||||
{
|
||||
if (elasticsearchConfiguration.Host == null)
|
||||
throw new InvalidOperationException("Elasticsearch endpoint is null!");
|
||||
|
||||
configuration.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri(elasticsearchConfiguration.Host))
|
||||
{
|
||||
// Yes I know this means they cannot use a self signed cert unless they also have authentication, but lets be real here
|
||||
// No one is going to be doing one of thsoe but not the other
|
||||
ModifyConnectionSettings = x => (!string.IsNullOrEmpty(elasticsearchConfiguration.Username) && !string.IsNullOrEmpty(elasticsearchConfiguration.Password)) ? x.BasicAuthentication(elasticsearchConfiguration.Username, elasticsearchConfiguration.Password).ServerCertificateValidationCallback((o, certificate, arg3, arg4) => { return true; }) : null,
|
||||
CustomFormatter = new EcsTextFormatter(),
|
||||
AutoRegisterTemplate = true,
|
||||
AutoRegisterTemplateVersion = AutoRegisterTemplateVersion.ESv7,
|
||||
IndexFormat = "tgs-logs",
|
||||
});
|
||||
}
|
||||
}
|
||||
if (elasticsearchSinkOptions != null)
|
||||
configuration.WriteTo.Elasticsearch(elasticsearchSinkOptions);
|
||||
|
||||
builder.AddSerilog(configuration.CreateLogger(), true);
|
||||
|
||||
@@ -222,6 +213,7 @@ namespace Tgstation.Server.Host.Extensions
|
||||
if (additionalLoggerProvider != null)
|
||||
builder.Services.TryAddEnumerable(additionalLoggerProvider);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the modifiable services to their default types.
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Extensions
|
||||
{
|
||||
@@ -18,46 +16,5 @@ namespace Tgstation.Server.Host.Extensions
|
||||
/// Gets a <see cref="Task"/> that never completes.
|
||||
/// </summary>
|
||||
public static Task InfiniteTask => InfiniteTaskCompletionSource.Task;
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="Task"/> that can be awaited while respecting a given <paramref name="cancellationToken"/>.
|
||||
/// </summary>
|
||||
/// <param name="task">The <see cref="Task"/> to add cancel support to.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
public static Task WithToken(this Task task, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(task);
|
||||
|
||||
async Task<object> Wrap()
|
||||
{
|
||||
await task;
|
||||
return null;
|
||||
}
|
||||
|
||||
return Wrap().WithToken(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="Task{TResult}"/> that can be awaited while respecting a given <paramref name="cancellationToken"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The result <see cref="Type"/> of the <paramref name="task"/>.</typeparam>
|
||||
/// <param name="task">The <see cref="Task{TResult}"/> to add cancel support to.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the result of <paramref name="task"/>.</returns>
|
||||
public static async Task<T> WithToken<T>(this Task<T> task, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(task);
|
||||
|
||||
var cancelTcs = new TaskCompletionSource();
|
||||
Task completedTask;
|
||||
using (cancellationToken.Register(() => cancelTcs.SetCanceled(cancellationToken)))
|
||||
completedTask = await Task.WhenAny(task, cancelTcs.Task);
|
||||
|
||||
if (completedTask != task)
|
||||
await cancelTcs.Task;
|
||||
|
||||
return await task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,19 +3,33 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.IO
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class Console : IConsole, IDisposable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Title
|
||||
{
|
||||
get => platformIdentifier.IsWindows
|
||||
? global::System.Console.Title
|
||||
: null;
|
||||
set => global::System.Console.Title = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Available => Environment.UserInteractive;
|
||||
|
||||
/// <inheritdoc />
|
||||
public CancellationToken CancelKeyPress => cancelKeyCts.Token;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPlatformIdentifier"/> for the <see cref="Console"/>.
|
||||
/// </summary>
|
||||
readonly IPlatformIdentifier platformIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CancellationTokenSource"/> for <see cref="CancelKeyPress"/>.
|
||||
/// </summary>
|
||||
@@ -29,8 +43,11 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Console"/> class.
|
||||
/// </summary>
|
||||
public Console()
|
||||
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</param>
|
||||
public Console(IPlatformIdentifier platformIdentifier)
|
||||
{
|
||||
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
|
||||
|
||||
cancelKeyCts = new CancellationTokenSource();
|
||||
global::System.Console.CancelKeyPress += (sender, e) =>
|
||||
{
|
||||
@@ -102,7 +119,7 @@ namespace Tgstation.Server.Host.IO
|
||||
cancellationToken,
|
||||
DefaultIOManager.BlockingTaskCreationOptions,
|
||||
TaskScheduler.Current)
|
||||
.WithToken(cancellationToken);
|
||||
.WaitAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task WriteAsync(string text, bool newLine, CancellationToken cancellationToken) => Task.Factory.StartNew(
|
||||
|
||||
@@ -389,7 +389,7 @@ namespace Tgstation.Server.Host.IO
|
||||
|
||||
async Task CopyThisFile()
|
||||
{
|
||||
await subdirCreationTask.WithToken(cancellationToken);
|
||||
await subdirCreationTask.WaitAsync(cancellationToken);
|
||||
using var lockContext = semaphore != null
|
||||
? await SemaphoreSlimContext.Lock(semaphore, cancellationToken)
|
||||
: null;
|
||||
|
||||
@@ -8,6 +8,11 @@ namespace Tgstation.Server.Host.IO
|
||||
/// </summary>
|
||||
interface IConsole
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IConsole"/> window's title. Can return <see langword="null"/> if getting the console title is not supported.
|
||||
/// </summary>
|
||||
string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="IConsole"/> is visible to the user.
|
||||
/// </summary>
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Tgstation.Server.Host.Jobs
|
||||
if (task == null)
|
||||
throw new InvalidOperationException("Job not started!");
|
||||
|
||||
return task.WithToken(cancellationToken);
|
||||
return task.WaitAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -265,7 +265,7 @@ namespace Tgstation.Server.Host.Jobs
|
||||
}
|
||||
|
||||
if (noMoreJobsShouldStart && !handler.Started)
|
||||
await Extensions.TaskExtensions.InfiniteTask.WithToken(cancellationToken);
|
||||
await Extensions.TaskExtensions.InfiniteTask.WaitAsync(cancellationToken);
|
||||
|
||||
Task cancelTask = null;
|
||||
using (jobCancellationToken.Register(() => cancelTask = CancelJob(job, canceller, true, cancellationToken)))
|
||||
@@ -320,7 +320,7 @@ namespace Tgstation.Server.Host.Jobs
|
||||
}
|
||||
}
|
||||
|
||||
var instanceCoreProvider = await activationTcs.Task.WithToken(cancellationToken);
|
||||
var instanceCoreProvider = await activationTcs.Task.WaitAsync(cancellationToken);
|
||||
|
||||
logger.LogTrace("Starting job...");
|
||||
await operation(
|
||||
|
||||
@@ -114,8 +114,12 @@ namespace Tgstation.Server.Host
|
||||
using (cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
|
||||
using (var fsWatcher = updatePath != null ? new FileSystemWatcher(Path.GetDirectoryName(updatePath)) : null)
|
||||
{
|
||||
if (fsWatcher != null)
|
||||
if (updatePath != null)
|
||||
{
|
||||
// If ever there is a NECESSARY update to the Host Watchdog, change this to use a pipe
|
||||
// I don't know why I'm only realizing this in 2023 when this is 2019 code
|
||||
// As it stands, FSWatchers use async I/O on Windows and block a new thread on Linux
|
||||
// That's an acceptable, if saddening, resource loss for now
|
||||
fsWatcher.Created += WatchForShutdownFileCreation;
|
||||
fsWatcher.EnableRaisingEvents = true;
|
||||
}
|
||||
@@ -241,7 +245,14 @@ namespace Tgstation.Server.Host
|
||||
public Task GracefulShutdown(bool detach) => RestartImpl(null, null, false, detach);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Die(Exception exception) => RestartImpl(null, exception, false, true);
|
||||
public Task Die(Exception exception)
|
||||
{
|
||||
if (exception != null)
|
||||
return RestartImpl(null, exception, false, true);
|
||||
|
||||
StopServerImmediate();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an <see cref="InvalidOperationException"/> if the <see cref="IServerControl"/> cannot be used.
|
||||
@@ -377,7 +388,7 @@ namespace Tgstation.Server.Host
|
||||
void StopServerImmediate()
|
||||
{
|
||||
shutdownInProgress = true;
|
||||
logger.LogTrace("Stopping host...");
|
||||
logger.LogDebug("Stopping host...");
|
||||
cancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
@@ -59,14 +60,27 @@ namespace Tgstation.Server.Host
|
||||
args[oldArgs.Length] = "--hostBuilder:reloadConfigOnChange=false";
|
||||
}
|
||||
|
||||
var basePath = IOManager.ResolvePath();
|
||||
const string AppSettings = "appsettings";
|
||||
const string AppSettingsRelocationKey = $"--{AppSettings}-base-path=";
|
||||
|
||||
var appsettingsRelativeBasePathArgument = args.FirstOrDefault(arg => arg.StartsWith(AppSettingsRelocationKey, StringComparison.Ordinal));
|
||||
string basePath;
|
||||
if (appsettingsRelativeBasePathArgument != null)
|
||||
basePath = IOManager.ResolvePath(appsettingsRelativeBasePathArgument[AppSettingsRelocationKey.Length..]);
|
||||
else
|
||||
basePath = IOManager.ResolvePath();
|
||||
|
||||
// this is a massive bloody hack but I don't know a better way to do it
|
||||
// It's needed for the setup wizard
|
||||
Environment.SetEnvironmentVariable($"{InternalConfiguration.Section}__{nameof(InternalConfiguration.AppSettingsBasePath)}", basePath);
|
||||
|
||||
IHostBuilder CreateDefaultBuilder() => Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
|
||||
.ConfigureAppConfiguration((context, builder) =>
|
||||
{
|
||||
builder.SetBasePath(basePath);
|
||||
|
||||
builder.AddYamlFile("appsettings.yml", optional: true, reloadOnChange: false)
|
||||
.AddYamlFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.yml", optional: true, reloadOnChange: false);
|
||||
builder.AddYamlFile($"{AppSettings}.yml", optional: true, reloadOnChange: false)
|
||||
.AddYamlFile($"{AppSettings}.{context.HostingEnvironment.EnvironmentName}.yml", optional: true, reloadOnChange: false);
|
||||
|
||||
// reorganize the builder so our yaml configs don't override the env/cmdline configs
|
||||
// values obtained via debugger
|
||||
|
||||
@@ -33,6 +33,11 @@ namespace Tgstation.Server.Host.Setup
|
||||
/// </summary>
|
||||
ElasticsearchConfiguration ElasticsearchConfiguration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Configuration.InternalConfiguration"/>.
|
||||
/// </summary>
|
||||
InternalConfiguration InternalConfiguration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPlatformIdentifier"/>.
|
||||
/// </summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user