Merge pull request #2028 from tgstation/PackageUpdates2

.NET 9 uber package update PR
This commit is contained in:
Jordan Dominion
2024-11-17 21:06:59 -05:00
committed by GitHub
33 changed files with 124 additions and 1134 deletions
+5 -1
View File
@@ -1276,7 +1276,7 @@ jobs:
path: ./code_coverage/integration_tests/windows_integration_tests_release_system_sqlite
- name: Upload Coverage to CodeCov
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
directory: ./code_coverage
fail_ci_if_error: true
@@ -1538,6 +1538,10 @@ jobs:
if (Test-Path -Path $log -PathType Leaf) {
Get-Content $log
}
$potentialMsiLog = [System.IO.Path]::GetFullPath("install_000_tgstation_server.msi.log")
if (Test-Path -Path $potentialMsiLog -PathType Leaf) {
Get-Content $potentialMsiLog
}
$installCode = $procMain.ExitCode
if($installCode -ne 0) {
Write-Host "ERROR INSTALLER EXIT CODE $installCode"
+2 -2
View File
@@ -18,9 +18,9 @@
<!-- Pinned: Be VERY careful about updating https://github.com/moq/moq/issues/1372 -->
<PackageReference Include="Moq" Version="4.20.72" />
<!-- Usage: MSTest execution -->
<PackageReference Include="MSTest.TestAdapter" Version="3.6.1" />
<PackageReference Include="MSTest.TestAdapter" Version="3.6.3" />
<!-- Usage: MSTest asserts etc... -->
<PackageReference Include="MSTest.TestFramework" Version="3.6.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.6.3" />
</ItemGroup>
</Project>
+2 -3
View File
@@ -8,14 +8,13 @@
<TgsRestVersion>10.12.0</TgsRestVersion>
<TgsGraphQLVersion>0.5.0</TgsGraphQLVersion>
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
<TgsApiLibraryVersion>16.3.0</TgsApiLibraryVersion>
<TgsClientVersion>19.3.0</TgsClientVersion>
<TgsApiLibraryVersion>17.0.0</TgsApiLibraryVersion>
<TgsClientVersion>20.0.0</TgsClientVersion>
<TgsDmapiVersion>7.3.0</TgsDmapiVersion>
<TgsInteropVersion>5.10.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.6.0</TgsHostWatchdogVersion>
<TgsSwarmProtocolVersion>8.0.0</TgsSwarmProtocolVersion>
<TgsContainerScriptVersion>1.2.1</TgsContainerScriptVersion>
<TgsMigratorVersion>2.0.0</TgsMigratorVersion>
<TgsNugetNetFramework>netstandard2.0</TgsNugetNetFramework>
<TgsNetMajorVersion>8</TgsNetMajorVersion>
<!-- Update this frequently with dotnet runtime patches. MAJOR MUST MATCH ABOVE! -->
@@ -1,7 +1,7 @@
<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="../../../../artifacts/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)" LocalizationFile="Theme.wxl" Theme="hyperlinkLicense" LogoFile="../../../../artifacts/tgs.ico" ShowVersion="yes" SuppressOptionsUI="yes" ThemeFile="Theme.xml" LaunchTarget="http://127.0.0.1:5000" />
<bal:WixStandardBootstrapperApplication LicenseUrl="https://github.com/tgstation/tgstation-server/tree/tgstation-server-v$(var.ProductVersion)" LocalizationFile="Theme.wxl" Theme="hyperlinkLicense" LogoFile="../../../../artifacts/tgs.ico" ShowVersion="yes" SuppressOptionsUI="yes" ThemeFile="Theme.xml" LaunchTarget="http://127.0.0.1:5000" />
</BootstrapperApplication>
<netfx:DotNetCoreSearch RuntimeType="aspnet" Platform="x64" MajorVersion="$(var.NetMajorVersion)" Variable="AspNetCorex64Status" />
@@ -16,7 +16,7 @@
/// <summary>
/// Package name
/// </summary>
/// <remarks>As much as I'd like to use Tgstation.Server.Common.Constants.CanonicalPackageName here, attempting to reference it makes Tgstation.Server.Migrator.Comms fail due to referencing the net2.0 version of that library. EVEN THOUGH IT'S A TRANSITIVE DEPENDENCY OF Tgstation.Server.Client!!!!! If that dead-ass tool has been removed, feel free to do this.</remarks>
/// <remarks>As much as I'd like to use Tgstation.Server.Common.Constants.CanonicalPackageName here, attempting to reference it makes VS go crazy with fake errors.</remarks>
const string CanonicalPackageName = "tgstation-server";
/// <summary>
@@ -48,6 +48,10 @@
</DirectorySearch>
</DirectorySearch>
</Property>
<ComponentGroup Id="ServiceHostWatchdogComponentGroup">
<Files Include="../../../../artifacts/Tgstation.Server.Host.Service/**" Directory="ApplicationDirectory" />
</ComponentGroup>
<StandardDirectory Id="ProgramFiles6432Folder">
<Directory Id="ApplicationDirectory" Name="!(bind.Property.ProductName)">
@@ -18,11 +18,6 @@
<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="5.0.2" />
@@ -28,9 +28,10 @@
<!-- Usage: HTTP constants reference -->
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
<!-- Usage: Decoding the 'nbf' property of JWTs -->
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.1.2" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.2.1" />
<!-- Usage: Data model annotating -->
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Text.Encodings.Web" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
@@ -9,8 +9,8 @@
<ItemGroup>
<!-- GraphQL connector and code generator -->
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="StrawberryShake.Server" Version="13.9.14" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.0" />
<PackageReference Include="StrawberryShake.Server" Version="14.1.0" />
</ItemGroup>
<ItemGroup>
@@ -11,9 +11,9 @@
<ItemGroup>
<!-- Usage: Connecting to SignalR hubs in API -->
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" />
<!-- Usage: Using target JSON serializer for API -->
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
@@ -12,7 +12,7 @@
<ItemGroup>
<!-- Usage: ValueTask netstandard backport -->
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.6.0" />
</ItemGroup>
<Target Name="IconGeneration" BeforeTargets="ResolveAssemblyReferences" Inputs="../../build/logo.svg" Outputs="../../artifacts/tgs.ico;../../artifacts/tgs.png;../../tools/Tgstation.Server.LogoGenerator/Program.cs">
@@ -13,9 +13,9 @@
<ItemGroup>
<!-- Usage: Identifying if we're running under SystemD -->
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="9.0.0" />
<!-- Usage: Console logging plugin -->
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
+2 -3
View File
@@ -190,10 +190,9 @@ namespace Tgstation.Server.Host.Service
// Mimicing Tgstation.Server.Host.Service.Wix here, which is the source of truth for this data
installer.Context = new InstallContext(
Path.Combine(programDataDirectory, $"tgs-install-{Guid.NewGuid()}.log"),
new[]
{
[
$"assemblypath=\"{exePath}\"{(String.IsNullOrWhiteSpace(PassthroughArgs) ? String.Empty : $" -p=\"{PassthroughArgs}\"")}",
});
]);
installer.Description = $"{Server.Common.Constants.CanonicalPackageName} running as a Windows service.";
installer.DisplayName = Server.Common.Constants.CanonicalPackageName;
installer.StartType = ServiceStartMode.Automatic;
@@ -21,15 +21,21 @@
<!-- Usage: Command line argument support -->
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
<!-- Usage: Identifies when we are running in the context of the Windows SCM -->
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.0" />
<!-- Usage: Windows event log logging plugin -->
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.EventLog" Version="9.0.0" />
<!-- Usage: Console logging plugin -->
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
<!-- Usage: Updated transitive dependency of Core.System.ServiceProcess -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<!-- Usage: Updated transitive dependency of Core.System.ServiceProcess -->
<PackageReference Include="System.Drawing.Common" Version="9.0.0" />
<!-- Usage: Updated transitive dependency, unable to tell what of -->
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
<!-- Usage: OS identification -->
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
<!-- Usage: Windows Service Manager intergration -->
<PackageReference Include="System.ServiceProcess.ServiceController" Version="8.0.1" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
@@ -10,7 +10,7 @@
<ItemGroup>
<!-- Usage: Logging abstractions -->
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
<!-- Usage: POSIX support for signals -->
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
</ItemGroup>
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.10",
"version": "8.0.11",
"commands": [
"dotnet-ef"
]
@@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BetterWin32Errors;
using Microsoft.Extensions.Logging;
using Serilog.Context;
@@ -1,10 +1,9 @@
using System;
using System.ComponentModel;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using BetterWin32Errors;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.IO
@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BetterWin32Errors;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
@@ -8,7 +9,6 @@ using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using BetterWin32Errors;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api.Models;
@@ -87,48 +87,46 @@
</Target>
<ItemGroup>
<!-- Usage: Concise throw statements for native Win32 errors -->
<PackageReference Include="BetterWin32Errors" Version="0.2.0" />
<!-- Usage: Interop with BYOND's /world/Topic -->
<PackageReference Include="Byond.TopicSender" Version="8.0.1" />
<!-- Usage: 503'ing request pipeline until server is finished initializing -->
<PackageReference Include="Cyberboss.AspNetCore.AsyncInitializer" Version="1.2.0" />
<!-- Usage: IRC interop -->
<PackageReference Include="Cyberboss.SmartIrc4net.Standard" Version="0.4.7" />
<PackageReference Include="Cyberboss.SmartIrc4net.Standard" Version="1.0.0" />
<!-- Usage: .env file parsing -->
<PackageReference Include="DotEnv.Core" Version="3.1.0" />
<!-- Usage: Text formatter for Elasticsearch logging plugin -->
<PackageReference Include="Elastic.CommonSchema.Serilog" Version="8.12.1" />
<PackageReference Include="Elastic.CommonSchema.Serilog" Version="8.12.2" />
<!-- Usage: GitLab interop -->
<PackageReference Include="GitLabApiClient" Version="1.8.0" />
<!-- Usage: GraphQL API Engine -->
<PackageReference Include="HotChocolate.AspNetCore" Version="14.0.0-rc.3.4" />
<PackageReference Include="HotChocolate.AspNetCore" Version="14.1.0" />
<!-- Usage: GraphQL Authorization Plugin -->
<PackageReference Include="HotChocolate.AspNetCore.Authorization" Version="14.0.0-rc.3.4" />
<PackageReference Include="HotChocolate.AspNetCore.Authorization" Version="14.1.0" />
<!-- Usage: GraphQL IDatabaseContext support -->
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="14.0.0-rc.3.4" />
<PackageReference Include="HotChocolate.Data.EntityFramework" Version="14.1.0" />
<!-- Usage: DataLoader source generation -->
<PackageReference Include="HotChocolate.Types.Analyzers" Version="14.0.0-rc.3.4" />
<PackageReference Include="HotChocolate.Types.Analyzers" Version="14.1.0" />
<!-- Usage: GraphQL additional scalar type definitions -->
<PackageReference Include="HotChocolate.Types.Scalars" Version="14.0.0-rc.3.4" />
<PackageReference Include="HotChocolate.Types.Scalars" Version="14.1.0" />
<!-- Usage: git interop -->
<PackageReference Include="LibGit2Sharp" Version="0.30.0" />
<!-- Usage: Support ""legacy"" Newotonsoft.Json in HTTP pipeline. The rest of our codebase uses Newtonsoft. -->
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.11" />
<!-- Usage: Using target JSON serializer for API -->
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="9.0.0" />
<!-- Usage: Generating dumps of dotnet engine processes -->
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.547301" />
<PackageReference Include="Microsoft.Diagnostics.NETCore.Client" Version="0.2.553101" />
<!-- Usage: Database ORM -->
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
<!-- Usage: Automatic migration generation using command line -->
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- Usage: Sqlite ORM plugin -->
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.11" />
<!-- Usage: MSSQL ORM plugin -->
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
<!-- Usage: POSIX support for syscalls, signals, and symlinks -->
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<!-- Usage: Cron string parsing -->
@@ -136,7 +134,7 @@
<!-- Usage: YAML config plugin -->
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
<!-- Usage: PostgresSQL ORM plugin -->
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.8" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.10" />
<!-- Usage: MYSQL/MariaDB ORM plugin -->
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<!-- Usage: Discord interop -->
@@ -144,7 +142,7 @@
<!-- Usage: Rich logger builder -->
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
<!-- Usage: Async logging plugin -->
<PackageReference Include="Serilog.Sinks.Async" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
<!-- Usage: Console logging plugin -->
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<!-- Usage: Elasticsearch logging plugin -->
@@ -152,15 +150,15 @@
<!-- Usage: File logging plugin -->
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
<!-- Usage: OpenAPI spec generator -->
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.9.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.0.0" />
<!-- Usage: Newtonsoft.Json plugin for OpenAPI spec generator -->
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.9.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="7.0.0" />
<!-- Usage: Windows authentication plugin allowing searching for users by name -->
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="8.0.1" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="9.0.0" />
<!-- Usage: Identifying owning user of Windows Process objects -->
<PackageReference Include="System.Management" Version="8.0.0" />
<PackageReference Include="System.Management" Version="9.0.0" />
<!-- Usage: Temporary resolution to compatibility issues with EFCore 7 and .NET 8 -->
<PackageReference Include="System.Security.Permissions" Version="8.0.0" />
<PackageReference Include="System.Security.Permissions" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
@@ -10,11 +10,11 @@
<ItemGroup>
<!-- Usage: JWT injection into HTTP pipeline -->
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.11" />
<!-- Usage: GitHub.com interop -->
<PackageReference Include="Octokit" Version="13.0.1" />
<!-- Usage: YAML conversion of Version objects -->
<PackageReference Include="YamlDotNet" Version="16.1.3" />
<PackageReference Include="YamlDotNet" Version="16.2.0" />
</ItemGroup>
<ItemGroup>
@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
@@ -7,7 +7,7 @@
<ItemGroup>
<!-- Usage: Creating mock database implementations -->
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.11" />
</ItemGroup>
<ItemGroup>
@@ -586,13 +586,13 @@ namespace Tgstation.Server.Tests.Live.Instance
// these can also happen
if (!(new PlatformIdentifier().IsWindows
&& (job.ExceptionDetails.Contains("BetterWin32Errors.Win32Exception: E_ACCESSDENIED: Access is denied.")
|| job.ExceptionDetails.Contains("BetterWin32Errors.Win32Exception: E_HANDLE: The handle is invalid.")
|| job.ExceptionDetails.Contains("BetterWin32Errors.Win32Exception: 3489660936: Unknown error (0xd0000008)")
|| job.ExceptionDetails.Contains("System.InvalidOperationException: No process is associated with this object.")
|| job.ExceptionDetails.Contains("BetterWin32Errors.Win32Exception: 2147942424: The program issued a command but the command length is incorrect.")
|| job.ExceptionDetails.Contains("BetterWin32Errors.Win32Exception: 2147942699: Only part of a ReadProcessMemory or WriteProcessMemory request was completed.")
|| job.ExceptionDetails.Contains("BetterWin32Errors.Win32Exception: 3489660964: Unknown error (0xd0000024)"))))
&& (job.ExceptionDetails.Contains("Access is denied.")
|| job.ExceptionDetails.Contains("The handle is invalid.")
|| job.ExceptionDetails.Contains("Unknown error")
|| job.ExceptionDetails.Contains("No process is associated with this object.")
|| job.ExceptionDetails.Contains("The program issued a command but the command length is incorrect.")
|| job.ExceptionDetails.Contains("Only part of a ReadProcessMemory or WriteProcessMemory request was completed.")
|| job.ExceptionDetails.Contains("Unknown error"))))
break;
var restartJob = await instanceClient.DreamDaemon.Restart(cancellationToken);
@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
+9 -33
View File
@@ -197,13 +197,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEM
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Tests.Signals", "tests\Tgstation.Server.Host.Tests.Signals\Tgstation.Server.Host.Tests.Signals.csproj", "{5813CC33-B16C-485D-A74D-20204DDF6542}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Migrator", "tools\Tgstation.Server.Migrator\Tgstation.Server.Migrator.csproj", "{CE499888-B22B-457C-891E-0EA9DC317228}"
ProjectSection(ProjectDependencies) = postProject
{07ED0FD5-E46B-4841-931D-BA2B673E16B2} = {07ED0FD5-E46B-4841-931D-BA2B673E16B2}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Migrator.Comms", "tools\Tgstation.Server.Migrator.Comms\Tgstation.Server.Migrator.Comms.csproj", "{07ED0FD5-E46B-4841-931D-BA2B673E16B2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Host.Common", "src\Tgstation.Server.Host.Common\Tgstation.Server.Host.Common.csproj", "{CF3968A0-EA81-4464-B2D4-C7D40F6B5BCB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Common", "src\Tgstation.Server.Common\Tgstation.Server.Common.csproj", "{70CD9A98-D31A-44A4-81D1-D02764CEEEFD}"
@@ -274,6 +267,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Shared.Tes
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Client.GraphQL", "src\Tgstation.Server.Client.GraphQL\Tgstation.Server.Client.GraphQL.csproj", "{8BF95E2D-FD27-470C-82B7-C21AC01BFBD7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nix", "nix", "{5130526C-A553-493B-A9B0-3DB452949886}"
ProjectSection(SolutionItems) = preProject
build\package\nix\flake.nix = build\package\nix\flake.nix
build\package\nix\package.nix = build\package\nix\package.nix
build\package\nix\ServerConsole.sha256 = build\package\nix\ServerConsole.sha256
build\package\nix\tgstation-server.nix = build\package\nix\tgstation-server.nix
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -448,30 +449,6 @@ Global
{5813CC33-B16C-485D-A74D-20204DDF6542}.ReleaseNoWindows|Any CPU.Build.0 = Release|Any CPU
{5813CC33-B16C-485D-A74D-20204DDF6542}.ReleaseNoWix|Any CPU.ActiveCfg = Release|Any CPU
{5813CC33-B16C-485D-A74D-20204DDF6542}.ReleaseNoWix|Any CPU.Build.0 = Release|Any CPU
{CE499888-B22B-457C-891E-0EA9DC317228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CE499888-B22B-457C-891E-0EA9DC317228}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CE499888-B22B-457C-891E-0EA9DC317228}.DebugNoWindows|Any CPU.ActiveCfg = Debug|Any CPU
{CE499888-B22B-457C-891E-0EA9DC317228}.DebugNoWindows|Any CPU.Build.0 = Debug|Any CPU
{CE499888-B22B-457C-891E-0EA9DC317228}.DebugNoWix|Any CPU.ActiveCfg = Debug|Any CPU
{CE499888-B22B-457C-891E-0EA9DC317228}.DebugNoWix|Any CPU.Build.0 = Debug|Any CPU
{CE499888-B22B-457C-891E-0EA9DC317228}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE499888-B22B-457C-891E-0EA9DC317228}.Release|Any CPU.Build.0 = Release|Any CPU
{CE499888-B22B-457C-891E-0EA9DC317228}.ReleaseNoWindows|Any CPU.ActiveCfg = Release|Any CPU
{CE499888-B22B-457C-891E-0EA9DC317228}.ReleaseNoWindows|Any CPU.Build.0 = Release|Any CPU
{CE499888-B22B-457C-891E-0EA9DC317228}.ReleaseNoWix|Any CPU.ActiveCfg = Release|Any CPU
{CE499888-B22B-457C-891E-0EA9DC317228}.ReleaseNoWix|Any CPU.Build.0 = Release|Any CPU
{07ED0FD5-E46B-4841-931D-BA2B673E16B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07ED0FD5-E46B-4841-931D-BA2B673E16B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07ED0FD5-E46B-4841-931D-BA2B673E16B2}.DebugNoWindows|Any CPU.ActiveCfg = Debug|Any CPU
{07ED0FD5-E46B-4841-931D-BA2B673E16B2}.DebugNoWindows|Any CPU.Build.0 = Debug|Any CPU
{07ED0FD5-E46B-4841-931D-BA2B673E16B2}.DebugNoWix|Any CPU.ActiveCfg = Debug|Any CPU
{07ED0FD5-E46B-4841-931D-BA2B673E16B2}.DebugNoWix|Any CPU.Build.0 = Debug|Any CPU
{07ED0FD5-E46B-4841-931D-BA2B673E16B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07ED0FD5-E46B-4841-931D-BA2B673E16B2}.Release|Any CPU.Build.0 = Release|Any CPU
{07ED0FD5-E46B-4841-931D-BA2B673E16B2}.ReleaseNoWindows|Any CPU.ActiveCfg = Release|Any CPU
{07ED0FD5-E46B-4841-931D-BA2B673E16B2}.ReleaseNoWindows|Any CPU.Build.0 = Release|Any CPU
{07ED0FD5-E46B-4841-931D-BA2B673E16B2}.ReleaseNoWix|Any CPU.ActiveCfg = Release|Any CPU
{07ED0FD5-E46B-4841-931D-BA2B673E16B2}.ReleaseNoWix|Any CPU.Build.0 = Release|Any CPU
{CF3968A0-EA81-4464-B2D4-C7D40F6B5BCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CF3968A0-EA81-4464-B2D4-C7D40F6B5BCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CF3968A0-EA81-4464-B2D4-C7D40F6B5BCB}.DebugNoWindows|Any CPU.ActiveCfg = Debug|Any CPU
@@ -605,8 +582,6 @@ Global
{103C61AB-67D6-46FE-AA47-CC633B88EE0F} = {82066812-6C73-4360-943B-B23F2F491261}
{CFFD7992-E73A-4D1F-9D7A-C817C07B7BEB} = {E82104F4-F5C4-4786-ACD4-B635166CDB21}
{5813CC33-B16C-485D-A74D-20204DDF6542} = {316141B0-CD21-4769-A013-D53DA9B9EC09}
{CE499888-B22B-457C-891E-0EA9DC317228} = {A55C1117-5808-4AB2-BEA6-4D4A3E66A2F2}
{07ED0FD5-E46B-4841-931D-BA2B673E16B2} = {A55C1117-5808-4AB2-BEA6-4D4A3E66A2F2}
{2648A85F-61AE-428E-95E1-66D06C7A3768} = {6FF654E6-3E2C-46D4-872D-D528F77D6973}
{457A1F89-6201-4430-BCC6-2F4438A54B9E} = {2648A85F-61AE-428E-95E1-66D06C7A3768}
{08E7C650-A447-4DE2-974E-ED123B50F8D6} = {457A1F89-6201-4430-BCC6-2F4438A54B9E}
@@ -619,6 +594,7 @@ Global
{69944039-65C2-40E1-9D86-0608FA0C2D70} = {A55C1117-5808-4AB2-BEA6-4D4A3E66A2F2}
{7F7FCFDF-271D-45C2-830C-BCCB19C57077} = {A55C1117-5808-4AB2-BEA6-4D4A3E66A2F2}
{EAB84FD0-5514-4254-B188-7D90ACB7284D} = {316141B0-CD21-4769-A013-D53DA9B9EC09}
{5130526C-A553-493B-A9B0-3DB452949886} = {2648A85F-61AE-428E-95E1-66D06C7A3768}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DFD36C95-3E49-41C7-ACDB-86BAF5B18A79}
@@ -3,6 +3,7 @@ using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Svg;
@@ -25,7 +26,45 @@ var whiteBgSvg = SvgDocument.Open<SvgDocument>(icoMs);
using var bitmap = svg.Draw(128, 128);
using var whiteBgBitmap = whiteBgSvg.Draw(160, 160);
using var icon = Icon.FromHandle(whiteBgBitmap.GetHicon());
// https://stackoverflow.com/questions/21387391/how-to-convert-an-image-to-an-icon-without-losing-transparency/21389253#21389253
// slight modifications
static Icon IconFromImage(Image img)
{
using var ms = new MemoryStream();
using var bw = new BinaryWriter(ms);
// Header
bw.Write((short)0); // 0 : reserved
bw.Write((short)1); // 2 : 1=ico, 2=cur
bw.Write((short)1); // 4 : number of images
// Image directory
var w = img.Width;
if (w >= 256) throw new InvalidOperationException("Width too big!");
bw.Write((byte)w); // 0 : width of image
var h = img.Height;
if (h >= 256) throw new InvalidOperationException("Height too big!");
bw.Write((byte)h); // 1 : height of image
bw.Write((byte)0); // 2 : number of colors in palette
bw.Write((byte)0); // 3 : reserved
bw.Write((short)0); // 4 : number of color planes
bw.Write((short)0); // 6 : bits per pixel
var sizeHere = ms.Position;
bw.Write(0); // 8 : image size
var start = (int)ms.Position + 4;
bw.Write(start); // 12: offset of image data
// Image data
img.Save(ms, ImageFormat.Png);
var imageSize = (int)ms.Position - start;
ms.Seek(sizeHere, SeekOrigin.Begin);
bw.Write(imageSize);
ms.Seek(0, SeekOrigin.Begin);
// And load it
return new Icon(ms);
}
using var icon = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? IconFromImage(whiteBgBitmap)
: Icon.FromHandle(whiteBgBitmap.GetHicon());
Directory.CreateDirectory("artifacts");
await using var iconStream = new FileStream("artifacts/tgs.ico", FileMode.Create);
@@ -1,389 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using TGS.Interface;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Api.Models.Request;
using Tgstation.Server.Client;
using Tgstation.Server.Common.Extensions;
static class Program
{
static async Task<int> Main(string[] args)
{
try
{
var tgs3Client = new Client();
switch (args[0])
{
case "--verify-connection":
var status = tgs3Client.ConnectionStatus(out var error);
if (status != ConnectivityLevel.Administrator)
{
Console.WriteLine($"Connection Error: {error}");
return 3;
}
return 0;
case "--migrate":
ushort apiPort = ushort.Parse(args[1]);
return await Migrate(tgs3Client, apiPort);
default:
return 2;
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
return 1;
}
}
static async Task<int> Migrate(IClient tgs3Client, ushort apiPort)
{
#if DEBUG
Console.WriteLine("Test log line...");
System.Diagnostics.Debugger.Launch();
#endif
Console.WriteLine("Connecting to TGS3...");
var status = tgs3Client.ConnectionStatus(out var tgs3Error);
if (status != ConnectivityLevel.Administrator)
{
Console.WriteLine($"Connection Error: {tgs3Client}");
return 13;
}
Console.WriteLine("Connected!");
Console.WriteLine("Connecting to TGS6...");
var assemblyName = Assembly.GetExecutingAssembly().GetName();
var productInfoHeaderValue =
new ProductInfoHeaderValue(
assemblyName.Name!,
assemblyName.Version!.Semver().ToString());
var serverUrl = new Uri($"http://localhost:{apiPort}");
var clientFactory = new RestServerClientFactory(productInfoHeaderValue.Product);
var TGS6Client = await clientFactory.CreateFromLogin(
serverUrl,
DefaultCredentials.AdminUserName,
DefaultCredentials.DefaultAdminUserPassword);
Console.WriteLine("Connected!");
// we do this synchronously and patiently because we aren't chumbii and this is delicate
// We need clear logs
var tgs3Instances = tgs3Client.Server.Instances.ToList();
foreach (var tgs3Instance in tgs3Instances)
{
var instanceName = tgs3Instance.Metadata.Name;
var instancePath = tgs3Instance.Metadata.Path;
if (!tgs3Instance.Metadata.Enabled)
{
Console.WriteLine($"Skipping instance {instanceName} at {instancePath}. Disabled.");
continue;
}
Console.WriteLine($"Migrating instance {instanceName} at {instancePath}");
RepositoryUpdateRequest? repositoryUpdateRequest = null;
if (tgs3Instance.Repository.Exists())
{
Console.WriteLine("Gathering instance repository data...");
repositoryUpdateRequest = new RepositoryUpdateRequest
{
CommitterEmail = tgs3Instance.Repository.GetCommitterEmail(),
CommitterName = tgs3Instance.Repository.GetCommitterName(),
UpdateSubmodules = true, // default in 3
Reference = tgs3Instance.Repository.GetBranch(out tgs3Error),
};
if (tgs3Error != null)
{
Console.WriteLine($"Error retrieving current branch: {tgs3Error}");
}
}
else
Console.WriteLine("Instance has no repository, that's fine.");
Console.WriteLine("Gather DreamDaemon and DreamMaker data...");
var dreamDaemonRequest = new DreamDaemonRequest
{
AllowWebClient = tgs3Instance.DreamDaemon.Webclient(),
AutoStart = tgs3Instance.DreamDaemon.Autostart(),
Port = tgs3Instance.DreamDaemon.Port(),
SecurityLevel = tgs3Instance.DreamDaemon.SecurityLevel() switch
{
TGS.Interface.DreamDaemonSecurity.Safe => Tgstation.Server.Api.Models.DreamDaemonSecurity.Safe,
TGS.Interface.DreamDaemonSecurity.Ultrasafe => Tgstation.Server.Api.Models.DreamDaemonSecurity.Ultrasafe,
_ => Tgstation.Server.Api.Models.DreamDaemonSecurity.Trusted,
}
};
var dreamMakerRequest = new DreamMakerRequest
{
ApiValidationSecurityLevel = dreamDaemonRequest.SecurityLevel,
ApiValidationPort = (ushort)(dreamDaemonRequest.Port + 111) // Best rotation we can do...
};
Console.WriteLine("Gathering chat data...");
var providerInfos = tgs3Instance.Chat.ProviderInfos();
var chatBotCreateRequests = new List<ChatBotCreateRequest>();
foreach(var providerInfo in providerInfos)
{
if (!providerInfo.Enabled)
continue;
var createRequest = new ChatBotCreateRequest()
{
Provider = providerInfo.Provider switch
{
TGS.Interface.ChatProvider.Discord => Tgstation.Server.Api.Models.ChatProvider.Discord,
_ => Tgstation.Server.Api.Models.ChatProvider.Irc,
},
Enabled = true,
ReconnectionInterval = 5,
};
var isDiscordProvider = createRequest.Provider == Tgstation.Server.Api.Models.ChatProvider.Discord;
createRequest.Name = isDiscordProvider
? "Discord Bot"
: "IRC Bot";
Console.WriteLine($"Gathering data for {createRequest.Name}...");
ChatConnectionStringBuilder csb;
if (createRequest.Provider == Tgstation.Server.Api.Models.ChatProvider.Discord)
{
var discordSetupInfo = new DiscordSetupInfo(providerInfo);
csb = new DiscordConnectionStringBuilder
{
DMOutputDisplay = DiscordDMOutputDisplayType.Always,
BotToken = discordSetupInfo.BotToken
};
}
else
{
var ircSetupInfo = new IRCSetupInfo(providerInfo);
csb = new IrcConnectionStringBuilder
{
Address = ircSetupInfo.URL,
Nickname = ircSetupInfo.Nickname,
Port = ircSetupInfo.Port
};
}
createRequest.ConnectionString = csb.ToString();
createRequest.Channels = new List<ChatChannel>();
static string NormalizeChannelId(string channelId) => channelId.ToLowerInvariant().Trim();
var distinctChannels = providerInfo.WatchdogChannels
.Union(providerInfo.DevChannels)
.Union(providerInfo.AdminChannels)
.Union(providerInfo.GameChannels)
.Select(NormalizeChannelId)
.Distinct();
foreach(var channelIdentifier in distinctChannels)
{
var newChatChannel = new ChatChannel
{
IsWatchdogChannel = providerInfo.WatchdogChannels.Any(x => NormalizeChannelId(x) == channelIdentifier),
IsAdminChannel = providerInfo.AdminChannels.Any(x => NormalizeChannelId(x) == channelIdentifier),
IsUpdatesChannel = providerInfo.DevChannels.Any(x => NormalizeChannelId(x) == channelIdentifier),
// system channels are too new a feature to target
ChannelData = channelIdentifier,
};
createRequest.Channels.Add(newChatChannel);
}
chatBotCreateRequests.Add(createRequest);
}
Console.WriteLine("Detaching TGS3 instance...");
tgs3Client.Server.InstanceManager.DetachInstance(instanceName);
Console.WriteLine("Creating TGS6 attach file...");
File.WriteAllText(Path.Combine(instancePath, "TGS4_ALLOW_INSTANCE_ATTACH"), String.Empty);
Console.WriteLine("Checking BYOND install...");
var byondDirectory = Path.Combine(instancePath, "BYOND");
var byondVersionFile = Path.Combine(byondDirectory, "byond_version.dat");
EngineVersionRequest? byondVersionRequest = null;
if (Directory.Exists(byondDirectory) && File.Exists(byondVersionFile))
{
var byondVersion = Version.Parse(File.ReadAllText(byondVersionFile).Trim());
Console.WriteLine($"Found installed BYOND version: {byondVersion.Major}.{byondVersion.Minor}");
byondVersionRequest = new EngineVersionRequest
{
EngineVersion = new EngineVersion
{
Version = byondVersion
}
};
}
var oldStaticDirectory = Path.Combine(instancePath, "Static");
var newConfigurationDirectory = Path.Combine(instancePath, "Configuration");
if (Directory.Exists(oldStaticDirectory))
{
Console.WriteLine("Migrating Static to Configuration/GameStaticFiles");
var gameStaticFilesDirectory = Path.Combine(newConfigurationDirectory, "GameStaticFiles");
Directory.CreateDirectory(newConfigurationDirectory);
Directory.Move(oldStaticDirectory, gameStaticFilesDirectory);
Console.WriteLine("Moving code modifications...");
var codeModsDirectory = Path.Combine(newConfigurationDirectory, "CodeModifications");
Directory.CreateDirectory(codeModsDirectory);
var allDmFiles = Directory.EnumerateFiles(gameStaticFilesDirectory, "*.dm", SearchOption.TopDirectoryOnly).ToList();
foreach (var dmFile in allDmFiles)
{
File.Move(Path.Combine(gameStaticFilesDirectory, dmFile), Path.Combine(codeModsDirectory, Path.GetFileName(dmFile)));
}
var allDmeFiles = Directory.EnumerateFiles(gameStaticFilesDirectory, "*.dme", SearchOption.TopDirectoryOnly).ToList();
if (allDmeFiles.Any())
{
foreach (var dmeFile in allDmeFiles)
{
File.Move(Path.Combine(gameStaticFilesDirectory, dmeFile), Path.Combine(codeModsDirectory, Path.GetFileName(dmeFile)));
}
}
else if (allDmFiles.Any())
{
Console.WriteLine("Generating HeadInclude.dm...");
var headIncludeBuilder = new StringBuilder();
foreach (var dmFile in allDmFiles.OrderBy(fileName => fileName.ToUpperInvariant()))
{
headIncludeBuilder.Append("#include \"");
headIncludeBuilder.Append(Path.GetFileName(dmFile));
headIncludeBuilder.Append("\"");
headIncludeBuilder.Append(Environment.NewLine);
}
File.WriteAllText(Path.Combine(codeModsDirectory, "HeadInclude.dm"), headIncludeBuilder.ToString());
}
}
var eventHandlersDirectory = Path.Combine(instancePath, "EventHandlers");
if (Directory.Exists(eventHandlersDirectory))
{
Console.WriteLine("Moving event scripts...");
Directory.CreateDirectory(newConfigurationDirectory);
Directory.Move(eventHandlersDirectory, Path.Combine(newConfigurationDirectory, "EventScripts"));
}
var diagnosticsDirectory = Path.Combine(instancePath, "Diagnostics");
var minidumpsDirectory = Path.Combine(diagnosticsDirectory, "Minidumps");
if (Directory.Exists(minidumpsDirectory))
{
Console.WriteLine("Renaming Minidumps folder to ProcessDumps...");
Directory.Move(minidumpsDirectory, Path.Combine(diagnosticsDirectory, "ProcessDumps"));
}
Console.WriteLine("Deleting BYOND folder...");
await RecursivelyDeleteDirectory(new DirectoryInfo(byondDirectory));
Console.WriteLine("Deleting RepoKey folder...");
await RecursivelyDeleteDirectory(new DirectoryInfo(Path.Combine(instancePath, "RepoKey")));
Console.WriteLine("Deleting Game folder...");
await RecursivelyDeleteDirectory(new DirectoryInfo(Path.Combine(instancePath, "Game")));
Console.WriteLine("Deleting Instance.json...");
File.Delete(Path.Combine(instancePath, "Instance.json"));
Console.WriteLine("Deleting prtestjob.json...");
File.Delete(Path.Combine(instancePath, "prtestjob.json"));
Console.WriteLine("Deleting TGS3.json...");
File.Delete(Path.Combine(instancePath, "TGS3.json"));
Console.WriteLine("Deleting TGDreamDaemonBridge.dll...");
File.Delete(Path.Combine(instancePath, "TGDreamDaemonBridge.dll"));
Console.WriteLine("Attaching TGS6 instance...");
var TGS6Instance = await TGS6Client.Instances.CreateOrAttach(new InstanceCreateRequest
{
ConfigurationType = ConfigurationType.Disallowed,
Name = instanceName,
Path = instancePath,
}, default);
Console.WriteLine($"Onlining TGS6 instance ID {TGS6Instance.Id}...");
TGS6Instance = await TGS6Client.Instances.Update(new InstanceUpdateRequest
{
Online = true,
Id = TGS6Instance.Id
}, default);
var v5InstanceClient = TGS6Client.Instances.CreateClient(TGS6Instance);
if (byondVersionRequest != null)
{
Console.WriteLine("Triggering BYOND install job...");
await v5InstanceClient.Engine.SetActiveVersion(byondVersionRequest, null, default);
}
if (repositoryUpdateRequest != null)
{
Console.WriteLine("Updating repository settings...");
await v5InstanceClient.Repository.Update(repositoryUpdateRequest, default);
}
Console.WriteLine("Updating deployment settings...");
await v5InstanceClient.DreamMaker.Update(dreamMakerRequest, default);
Console.WriteLine("Updating DreamDaemon settings...");
await v5InstanceClient.DreamDaemon.Update(dreamDaemonRequest, default);
foreach(var chatBotCreateRequest in chatBotCreateRequests)
{
Console.WriteLine($"Creating chat bot {chatBotCreateRequest.Name}...");
await v5InstanceClient.ChatBots.Create(chatBotCreateRequest, default);
}
Console.WriteLine($"Instance {instanceName} (TGS6 ID: {TGS6Instance.Id}) successfully migrated!");
}
Console.WriteLine("All enabled V3 instances migrated into V5 and detached from V3!");
return 0;
}
static async Task RecursivelyDeleteDirectory(DirectoryInfo dir)
{
var tasks = new List<Task>();
if (!dir.Exists)
return;
// check if we are a symbolic link
if (!dir.Attributes.HasFlag(FileAttributes.Directory) || dir.Attributes.HasFlag(FileAttributes.ReparsePoint))
{
dir.Delete();
return;
}
foreach (var subDir in dir.EnumerateDirectories())
tasks.Add(RecursivelyDeleteDirectory(subDir));
foreach (var file in dir.EnumerateFiles())
{
file.Attributes = FileAttributes.Normal;
file.Delete();
}
await Task.WhenAll(tasks);
dir.Delete(true);
}
}
@@ -1,24 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../build/Common.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net472</TargetFramework>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<Version>$(TgsMigratorVersion)</Version>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TGServiceInterface" Version="3.2.6" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Tgstation.Server.Client\Tgstation.Server.Client.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Net.Http" />
</ItemGroup>
</Project>
-544
View File
@@ -1,544 +0,0 @@
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration.Install;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Management;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Security.Principal;
using System.ServiceProcess;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Octokit;
using Tgstation.Server.Api;
using Tgstation.Server.Client;
using Tgstation.Server.Common;
using Tgstation.Server.Common.Extensions;
using Tgstation.Server.Common.Http;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Setup;
using Tgstation.Server.Migrator.Properties;
using FileMode = System.IO.FileMode;
[DoesNotReturn]
static void ExitPause(int exitCode)
{
Console.WriteLine("Consider saving the console text to report issues. Press any key to exit...");
Console.ReadKey();
Environment.Exit(exitCode);
}
try
{
var commandLine = Environment.GetCommandLineArgs();
var commandLineArguments = commandLine.Skip(1);
var skipPreamble = commandLineArguments.Any(x => x.Equals("--skip-preamble", StringComparison.OrdinalIgnoreCase));
Console.WriteLine("This is a very straightfoward script to migrate the instances of a TGS3 install into a new TGS6 install");
static bool PromptYesOrNo(string question)
{
Console.Write($"{question} (y/n):");
var character = Console.ReadKey();
Console.WriteLine();
return character.KeyChar.ToString().ToUpperInvariant() == "Y";
}
// WORKING DIRECTORY CHECK
var currentAssembly = Assembly.GetExecutingAssembly();
if(Path.GetDirectoryName(Path.GetFullPath(currentAssembly.Location))!.Replace("\\", "/").ToUpperInvariant()
!= Path.GetFullPath(Environment.CurrentDirectory).Replace("\\", "/").ToUpperInvariant())
{
Console.WriteLine("Please keep the working directory equivalent to the program directory for this migration!");
ExitPause(8);
}
// PREREQUISITE CHECK
if (!skipPreamble)
{
Console.WriteLine("We need to ensure you're running this program as Administrator because there are several operations we'll do that require it.");
Console.WriteLine("If not, you will be prompted to elevate this process.");
}
// ADMINISTRATOR CHECK
static bool IsAdministrator()
{
using var identity = WindowsIdentity.GetCurrent();
var principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
if (!IsAdministrator())
{
Console.WriteLine("Not running as admin. Elevating process...");
var selfExecutable = commandLine.First().Replace(".dll", ".exe");
var selfArguments = String.Join(" ", commandLineArguments) + " --skip-preamble";
using var elevatedProcess = new Process();
elevatedProcess.StartInfo.UseShellExecute = true;
elevatedProcess.StartInfo.FileName = selfExecutable;
elevatedProcess.StartInfo.Arguments = selfArguments;
elevatedProcess.StartInfo.Verb = "runas";
elevatedProcess.Start();
return;
}
Console.WriteLine("Administrative privileges confirmed.");
// TGS3 SERVICE CHECK
const string PathToCommsBinary =
#if DEBUG
"../../../../../Tgstation.Server.Migrator.Comms/bin/Debug/net472/win-x86/" +
#else
"Comms/" +
#endif
"Tgstation.Server.Migrator.Comms.exe";
Console.WriteLine($"Checking {PathToCommsBinary} exists...");
if (!File.Exists(PathToCommsBinary))
{
Console.WriteLine("Could not find WCF comms binary!");
ExitPause(7);
}
static int RunComms(string command)
{
using var commsProcess = new Process();
commsProcess.StartInfo.FileName = PathToCommsBinary;
commsProcess.StartInfo.Arguments = command;
commsProcess.Start();
commsProcess.WaitForExit();
return commsProcess.ExitCode;
}
Console.WriteLine("Checking for TGS3 service...");
const string OldServiceName = "TG Station Server";
const string NewServiceName = Constants.CanonicalPackageName;
static ServiceController GetTgs3Service(bool checkNewOneIsntInstalled)
{
var allServices = ServiceController.GetServices();
var tgs3Service = allServices.FirstOrDefault(service => service.ServiceName == OldServiceName);
foreach (var service in allServices)
{
if (service == tgs3Service)
continue;
using (service)
if (checkNewOneIsntInstalled && (service.ServiceName == NewServiceName || service.ServiceName == "tgstation-server-4"))
{
Console.WriteLine("Detected existing TGS4+ install! Cannot continue. Please uninstall any versions of TGS4+ before continuing.");
ExitPause(10);
}
}
if (checkNewOneIsntInstalled)
Console.WriteLine("TGS4+ service install not detected.");
if (tgs3Service == null)
{
Console.WriteLine("TGS3 is not installed on this machine!");
ExitPause(5);
}
return tgs3Service;
}
using var tgs3Service = GetTgs3Service(true);
if (tgs3Service.Status != ServiceControllerStatus.Running)
{
Console.WriteLine("TGS3 service is installed but not running! Please start the service before continuing.");
ExitPause(9);
}
// TGS3 CONNECTION CHECK
Console.WriteLine("Checking TGS3 connection...");
var commsExitCode = RunComms("--verify-connection");
if(commsExitCode != 0)
{
Console.WriteLine("Could not connect to TGS3 as administrator!");
ExitPause(6);
}
// USER INPUT
Console.WriteLine("We've confirmed you have have both TGS3 installed and TGS4+ service UNinstalled on THIS machine.");
Console.WriteLine();
Console.WriteLine("Please read all of the following CAREFULLY before proceeding:");
Console.WriteLine("Confirm you want to migrate to the latest version installing the necessary prerequisite .NET version along the way.");
Console.WriteLine("Please note that this is a one way upgrade and will not keep your DreamDaemon servers running throughout it.");
Console.WriteLine("All TGS3 instances will be migrated in place. The following components will be preserved:");
Console.WriteLine("- Repository (No test merge data or SSH key)");
Console.WriteLine("- BYOND version (redownloaded from byond.com)");
Console.WriteLine("- A FEW server configuration settings (Committer info, Autostart, Webclient, Game Port, Security Level)");
Console.WriteLine("- EventHandlers");
Console.WriteLine("- Chat Bots, if enabled");
Console.WriteLine(" - TGS4+ doesn't support individual user/group identification. Admin channels will be used instead");
Console.WriteLine(" - IRC authentication information cannot be copied and must be manually adjusted");
Console.WriteLine("- Static Files");
Console.WriteLine("- Code Modifications");
Console.WriteLine("Remaining components such as logins, game builds, etc. can be recreated once the migration is complete.");
Console.WriteLine("IMPORTANT NOTES:");
Console.WriteLine("- INSTANCES CANNOT HAVE GAME PORTS OFFSET 111 UNITS FROM EACH OTHER OR HIGHER THAN 65423! WE AREN'T CORRECTING FOR THIS WHILE MIGRATING!");
Console.WriteLine("- DISABLED INSTANCES WILL NOT BE MIGRATED! PLEASE ENABLE ALL INSTANCES YOU WISH TO MIGRATE BEFORE CONTINUING!");
Console.WriteLine("- INSTANCE AUTO UPDATE CAN INTERFERE WITH THE MIGRATION! PLEASE DISABLE IT ON ALL INSTANCES BEING MIGRATED BEFORE CONTINUING!");
Console.WriteLine("- DO NOT ATTEMPT TO USE TGS3 VIA NORMAL METHODS WHILE THIS MIGRATION IS TAKING PLACE OR YOU COULD CORRUPT YOUR DATA!");
Console.WriteLine("Side note: You can skip the TGS6 setup wizard step by copying your premade appsettings.Production.yml file next to this .exe NOW.");
if (!PromptYesOrNo("Proceed with upgrade?"))
{
Console.WriteLine("Prerequisite not met.");
ExitPause(0);
}
string? tgsInstallPath = null;
do
{
Console.WriteLine("Please enter the directory where you would like the TGS binaries installed.");
Console.Write("This may be anywhere but should be empty: ");
tgsInstallPath = Console.ReadLine();
if (!String.IsNullOrWhiteSpace(tgsInstallPath))
{
if (!Path.IsPathRooted(tgsInstallPath))
{
Console.WriteLine("Please do not use a relative path for this. Enter the full path including the drive letter.");
tgsInstallPath = null;
}
else if (Path.GetInvalidPathChars().Any(invalidChar => tgsInstallPath.Contains(invalidChar)))
{
Console.WriteLine("Invalid characters detected!");
tgsInstallPath = null;
}
}
}
while (String.IsNullOrWhiteSpace(tgsInstallPath));
Console.WriteLine("Attempting to create TGS install directory...");
Directory.CreateDirectory(tgsInstallPath);
// ASP.NET 8.0 RUNTIME CHECK
Console.WriteLine("Next step, we need to ensure the ASP.NET Core 6 runtime is installed on your machine.");
Console.WriteLine("We're going to download it for you.");
Console.WriteLine("Yes, this program runs .NET 6, but it contains the entire runtime embedded into it. You will need a system-wide install for TGS.");
var runtimeInstalled = true; // assume for now
using (var dotnetRuntimeCheck = new Process())
{
dotnetRuntimeCheck.StartInfo.FileName = "C:/Program Files/dotnet/dotnet.exe";
dotnetRuntimeCheck.StartInfo.Arguments = "--list-runtimes";
dotnetRuntimeCheck.StartInfo.RedirectStandardOutput = true;
try
{
dotnetRuntimeCheck.Start();
dotnetRuntimeCheck.WaitForExit();
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 2)
{
Console.WriteLine("Dotnet does not appear to be installed at all.");
runtimeInstalled = false;
}
if (runtimeInstalled)
{
var versions = await dotnetRuntimeCheck.StandardOutput.ReadToEndAsync();
var regex = new Regex("Microsoft\\.AspNetCore\\.App 8\\.0\\.[0-9]+");
if (!regex.IsMatch(versions))
runtimeInstalled = false;
}
}
// ASP.NET 8.0 RUNTIME SETUP
var assemblyName = currentAssembly.GetName();
var productInfoHeaderValue =
new ProductInfoHeaderValue(
assemblyName.Name!,
assemblyName.Version!.Semver().ToString());
var httpClientFactory = new HttpClientFactory(productInfoHeaderValue);
if (!runtimeInstalled)
{
// RUNTIME DONWLOAD
Console.WriteLine("The version we are installing is the latest circa 26-09-2022, feel free to update it later if you want but that is not necessary.");
var x64 = Environment.Is64BitOperatingSystem;
var xSubstitution = x64 ? "64" : "86";
Console.WriteLine($"Running on an x{xSubstitution} system.");
var downloadUri = RuntimeDistributableAttribute.Instance.RuntimeDistributableUrl;
var dotnetDownloadFilePath = $"dotnet-hosting-bundle-installer.exe";
Console.WriteLine($"Downloading {downloadUri} to {Path.GetFullPath(dotnetDownloadFilePath)}...");
using var httpClient = httpClientFactory.CreateClient();
using var request = new HttpRequestMessage(HttpMethod.Get, downloadUri);
var webRequestTask = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, default);
using var response = await webRequestTask;
response.EnsureSuccessStatusCode();
await using (var responseStream = await response.Content.ReadAsStreamAsync())
{
await using var fileStream = new FileStream(
dotnetDownloadFilePath,
FileMode.Create,
FileAccess.Write,
FileShare.ReadWrite | FileShare.Delete,
4096,
FileOptions.Asynchronous | FileOptions.SequentialScan);
await responseStream.CopyToAsync(fileStream);
}
// RUNTIME INSTALLATION
Console.WriteLine("Runtime downloaded. Running silent installation...");
bool silentInstallSuccess = true;
using var silentInstallProcess = new Process();
{
silentInstallProcess.StartInfo.UseShellExecute = false;
silentInstallProcess.StartInfo.FileName = dotnetDownloadFilePath;
silentInstallProcess.StartInfo.Arguments = "/install /quiet /norestart";
silentInstallProcess.Start();
silentInstallProcess.WaitForExit();
if (silentInstallProcess.ExitCode != 0)
{
Console.WriteLine("Silent installation failed! Please install the runtime interactively.");
Console.WriteLine("Launching install dialog");
silentInstallSuccess = false;
}
}
if (!silentInstallSuccess)
{
using var installProcess = new Process();
installProcess.StartInfo.FileName = dotnetDownloadFilePath;
installProcess.Start();
installProcess.WaitForExit();
if (!PromptYesOrNo("Was the installation successful?"))
{
Console.WriteLine("Cannot continue without ASP.NET 8.0 runtime installed.");
ExitPause(2);
}
}
}
else
{
Console.WriteLine("Runtime detected successfully. Continuing...");
}
// TGS6 ONLINE LOCATING
Console.WriteLine("Now we're going to locate the latest version of the TGS service.");
Console.WriteLine("(This migrator does not support the console runner, but you may switch the installation to it after completion)");
Console.WriteLine("Determining latest version of TGS 5.X.X...");
var gitHubClient = new GitHubClient(new Octokit.ProductHeaderValue(productInfoHeaderValue.Product!.Name, productInfoHeaderValue.Product.Version));
string? gitHubPat = Environment.GetEnvironmentVariable("TGS_MIGRATOR_GITHUB_PAT");
if (gitHubPat != null)
gitHubClient.Credentials = new Credentials(gitHubPat);
const int TgstationServerRepoId = 92952846;
var allReleases = await gitHubClient.Repository.Release.GetAll(TgstationServerRepoId);
const string VersionFiveTagPrefix = "tgstation-server-v5.";
var allVersionFiveReleases = allReleases
.Where(release => release.TagName.StartsWith(VersionFiveTagPrefix));
var latestVersionFiveRelease = allVersionFiveReleases
.OrderByDescending(release => Version.Parse(release.TagName[(VersionFiveTagPrefix.Length - 2)..]))
.FirstOrDefault();
if (latestVersionFiveRelease == null)
{
Console.WriteLine("Unable to determine latest version 5 release!");
ExitPause(3);
}
Console.WriteLine($"Latest V5 version: {latestVersionFiveRelease.TagName}");
var serverServiceAsset = latestVersionFiveRelease.Assets.FirstOrDefault(asset => asset.Name == "ServerService.zip");
if (serverServiceAsset == null)
{
Console.WriteLine("Unable to determine ServerService.zip release asset!");
ExitPause(4);
}
// TGS6 SETUP WIZARD
Console.WriteLine("We are now going to run the TGS setup wizard to generate your new server configuration file.");
var serverFactory = Tgstation.Server.Host.Core.Application.CreateDefaultServerFactory();
_ = await serverFactory.CreateServer(new[] { $"General:SetupWizardMode={SetupWizardMode.Only}" }, null, default); // This is where the wizard actually runs
// TGS6 DOWNLOAD AND UNZIP
Console.WriteLine("Downloading TGS6...");
using (var loggerFactory = LoggerFactory.Create(builder => { }))
{
BufferedFileStreamProvider tgsFiveZipBuffer;
{
var fileDownloader = new FileDownloader(httpClientFactory, loggerFactory.CreateLogger<FileDownloader>());
await using var tgsFiveZipDownload = fileDownloader.DownloadFile(new Uri(serverServiceAsset.BrowserDownloadUrl), null);
tgsFiveZipBuffer = new BufferedFileStreamProvider(
await tgsFiveZipDownload.GetResult(default));
}
await using (tgsFiveZipBuffer)
{
Console.WriteLine("Unzipping TGS6...");
await serverFactory.IOManager.ZipToDirectory(
tgsInstallPath,
await tgsFiveZipBuffer.GetResult(default),
default);
}
}
// TGS6 CONFIG SETUP
const string ConfigurationFileName = "appsettings.Production.yml";
Console.WriteLine("Extracting API port from configuration...");
ushort configuredApiPort;
{
var configFileContents = await File.ReadAllTextAsync(ConfigurationFileName);
var match = Regex.Match(configFileContents, "ApiPort: ([0-9]+)");
if (!match.Success)
{
Console.WriteLine("Unable to extract ApiPort setting!");
ExitPause(12);
}
configuredApiPort = ushort.Parse(match.Groups[1].Value);
}
Console.WriteLine("Moving configuration file from setup wizard to installation folder...");
File.Copy(ConfigurationFileName, Path.Combine(tgsInstallPath, ConfigurationFileName));
// TGS6 SERVICE SETUP
Console.WriteLine("Installing TGS6 service...");
using (var processInstaller = new ServiceProcessInstaller())
using (var installer = new ServiceInstaller())
{
processInstaller.Account = ServiceAccount.LocalSystem;
installer.Context = new InstallContext(
"tgs-migrate-install.log",
new string[]
{
$"assemblypath={Path.Combine(tgsInstallPath, "Tgstation.Server.Host.Service.exe")}"
});
installer.Description = "/tg/station 13 server running as a windows service";
installer.DisplayName = "tgstation-server";
installer.StartType = ServiceStartMode.Automatic;
installer.ServicesDependedOn = new string[] { "Tcpip", "Dhcp", "Dnscache" };
installer.ServiceName = NewServiceName;
installer.Parent = processInstaller;
var state = new ListDictionary();
installer.Install(state);
}
Console.WriteLine("Starting TGS6 service...");
var allServices = ServiceController.GetServices();
using (var TGS6Service = allServices.FirstOrDefault(service => service.ServiceName == NewServiceName))
{
if (TGS6Service == null)
{
Console.WriteLine("Unable to locate newly installed TGS6 service!");
ExitPause(11);
}
foreach (var service in allServices)
{
if (service == TGS6Service)
continue;
service.Dispose();
}
TGS6Service.Start();
TGS6Service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromMinutes(2));
}
// TGS6 CLIENT CONNECTION
const int MaxWaitMinutes = 5;
Console.WriteLine($"Connecting to TGS6 (Max {MaxWaitMinutes} minute wait)...");
var giveUpAt = DateTimeOffset.UtcNow.AddMinutes(MaxWaitMinutes);
var serverUrl = new Uri($"http://localhost:{configuredApiPort}");
var clientFactory = new RestServerClientFactory(productInfoHeaderValue.Product);
IRestServerClient TGS6Client;
for (var I = 1; ; ++I)
{
try
{
Console.WriteLine($"Attempt {I}...");
TGS6Client = await clientFactory.CreateFromLogin(
serverUrl,
DefaultCredentials.AdminUserName,
DefaultCredentials.DefaultAdminUserPassword);
break;
}
catch (HttpRequestException)
{
//migrating, to be expected
if (DateTimeOffset.UtcNow > giveUpAt)
throw;
await Task.Delay(TimeSpan.FromSeconds(1));
}
catch (ServiceUnavailableException)
{
// migrating, to be expected
if (DateTimeOffset.UtcNow > giveUpAt)
throw;
await Task.Delay(TimeSpan.FromSeconds(1));
}
}
Console.WriteLine("Successfully connected to TGS6!");
// COMMS MIGRATION
Console.WriteLine("Deferring to Comms binary to migrate instances...");
commsExitCode = RunComms($"--migrate {configuredApiPort}");
if (commsExitCode != 0)
{
Console.WriteLine("Could not connect to TGS3 as administrator!");
ExitPause(commsExitCode);
}
// TGS3 SHUTDOWN
Console.WriteLine("Shutting down TGS3 service...");
tgs3Service.Stop();
tgs3Service.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMinutes(2));
tgs3Service.Dispose();
Console.WriteLine("Disabling TGS3 service...");
using (var managementObject = new ManagementObject(string.Format("Win32_Service.Name=\"{0}\"", OldServiceName)))
{
managementObject.InvokeMethod("ChangeStartMode", new object[] { "Disabled" });
}
if(tgs3Service.StartType != ServiceStartMode.Disabled)
Console.WriteLine("Failed to disable TGS3 service! This isn't critical, however.");
Console.WriteLine("Migration complete! Please continue uninstall TGS3 using Add/Remove Programs.");
Console.WriteLine("Then configure TGS6 using an interactive client to build and start your server.");
ExitPause(0);
}
catch (Exception ex)
{
Console.WriteLine("An error occurred in the migration!");
Console.WriteLine(ex);
ExitPause(1);
}
@@ -1,34 +0,0 @@
using System;
using System.Reflection;
namespace Tgstation.Server.Migrator.Properties
{
/// <summary>
/// Attribute for bringing in the runtime redistributable download link
/// </summary>
[AttributeUsage(AttributeTargets.Assembly)]
sealed class RuntimeDistributableAttribute : Attribute
{
/// <summary>
/// Return the <see cref="Assembly"/>'s instance of the <see cref="MasterVersionsAttribute"/>.
/// </summary>
public static RuntimeDistributableAttribute Instance => Assembly
.GetExecutingAssembly()
.GetCustomAttribute<RuntimeDistributableAttribute>()!;
/// <summary>
/// The <see cref="Uri"/> of the current runtime distributable.
/// </summary>
public Uri RuntimeDistributableUrl { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MasterVersionsAttribute"/> class.
/// </summary>
/// <param name="runtimeDistributableUrl">The <see cref="string"/> value of <see cref="RuntimeDistributableUrl"/>.</param>
public RuntimeDistributableAttribute(
string runtimeDistributableUrl)
{
RuntimeDistributableUrl = new Uri(runtimeDistributableUrl ?? throw new ArgumentNullException(nameof(runtimeDistributableUrl)));
}
}
}
@@ -1,37 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../build/Common.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(TgsFrameworkVersion)</TargetFramework> <!-- This is fine, we publish self-contained -->
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<Version>$(TgsMigratorVersion)</Version>
<Nullable>enable</Nullable>
<NoWarn>CA1416</NoWarn>
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Core.System.ServiceProcess" Version="2.0.1" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
</ItemGroup>
<Target Name="ApplyRuntimeDistributableAttribute" BeforeTargets="CoreCompile">
<ItemGroup>
<AssemblyAttributes Include="Tgstation.Server.Migrator.Properties.RuntimeDistributableAttribute">
<_Parameter1>$(TgsDotnetRedistUrl)</_Parameter1>
</AssemblyAttributes>
</ItemGroup>
<WriteCodeFragment AssemblyAttributes="@(AssemblyAttributes)" Language="C#" OutputDirectory="$(IntermediateOutputPath)" OutputFile="RuntimeDistributableAssemblyInfo.cs">
<Output TaskParameter="OutputFile" ItemName="Compile" />
<Output TaskParameter="OutputFile" ItemName="FileWrites" />
</WriteCodeFragment>
</Target>
<ItemGroup>
<ProjectReference Include="..\..\src\Tgstation.Server.Client\Tgstation.Server.Client.csproj" />
<ProjectReference Include="..\..\src\Tgstation.Server.Host\Tgstation.Server.Host.csproj" />
</ItemGroup>
</Project>