mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-27 15:07:03 +01:00
Remove the TGS3 migrator
This commit is contained in:
@@ -15,7 +15,6 @@
|
||||
<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! -->
|
||||
|
||||
+8
-13
@@ -4,6 +4,7 @@
|
||||
using System.IO;
|
||||
using System.ServiceProcess;
|
||||
|
||||
using Tgstation.Server.Common;
|
||||
using Tgstation.Server.Host.Common;
|
||||
|
||||
using WixToolset.Dtf.WindowsInstaller;
|
||||
@@ -13,12 +14,6 @@
|
||||
/// </summary>
|
||||
public static class InstallationExtensions
|
||||
{
|
||||
/// <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>
|
||||
const string CanonicalPackageName = "tgstation-server";
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to detach stop the existing tgstation-server service if it exists.
|
||||
/// </summary>
|
||||
@@ -35,18 +30,18 @@
|
||||
session.Log("Begin DetachStopTgsServiceIfRunning");
|
||||
ServiceController serviceController = null;
|
||||
|
||||
session.Log($"Searching for {CanonicalPackageName} service...");
|
||||
session.Log($"Searching for {Constants.CanonicalPackageName} service...");
|
||||
try
|
||||
{
|
||||
foreach (var controller in ServiceController.GetServices())
|
||||
if (controller.ServiceName == CanonicalPackageName)
|
||||
if (controller.ServiceName == Constants.CanonicalPackageName)
|
||||
serviceController = controller;
|
||||
else
|
||||
controller.Dispose();
|
||||
|
||||
if (serviceController == null || serviceController.Status != ServiceControllerStatus.Running)
|
||||
{
|
||||
session.Log($"{CanonicalPackageName} service not found. Continuing.");
|
||||
session.Log($"{Constants.CanonicalPackageName} service not found. Continuing.");
|
||||
return ActionResult.Success;
|
||||
}
|
||||
|
||||
@@ -54,16 +49,16 @@
|
||||
PipeCommands.CommandDetachingShutdown)
|
||||
.Value;
|
||||
|
||||
session.Log($"{CanonicalPackageName} service found. Sending command \"{PipeCommands.CommandDetachingShutdown}\" ({commandId})...");
|
||||
session.Log($"{Constants.CanonicalPackageName} service found. Sending command \"{PipeCommands.CommandDetachingShutdown}\" ({commandId})...");
|
||||
|
||||
serviceController.ExecuteCommand(commandId);
|
||||
|
||||
session.Log($"Command sent. Waiting for {CanonicalPackageName} service to stop...");
|
||||
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($"{CanonicalPackageName} stopped {(stopped ? String.Empty : "un")}successfully.");
|
||||
session.Log($"{Constants.CanonicalPackageName} stopped {(stopped ? String.Empty : "un")}successfully.");
|
||||
|
||||
return stopped
|
||||
? ActionResult.Success
|
||||
@@ -97,7 +92,7 @@
|
||||
session.Log("Begin ApplyProductionAppsettingsIfNonExistant");
|
||||
var programDataDirectory = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
||||
CanonicalPackageName);
|
||||
Constants.CanonicalPackageName);
|
||||
var initialAppSettingsPath = Path.Combine(programDataDirectory, "appsettings.Initial.yml");
|
||||
var productionAppSettingsPath = Path.Combine(programDataDirectory, "appsettings.Production.yml");
|
||||
|
||||
|
||||
+1
@@ -11,6 +11,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Tgstation.Server.Common\Tgstation.Server.Common.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Tgstation.Server.Host.Common\Tgstation.Server.Host.Common.csproj">
|
||||
<Private>True</Private>
|
||||
</ProjectReference>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#if NETSTANDARD2_0_OR_GREATER
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
@@ -155,3 +156,4 @@ namespace Tgstation.Server.Common.Extensions
|
||||
public static ValueTask WhenAll(params ValueTask[] tasks) => WhenAll((IReadOnlyList<ValueTask>)tasks);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#if NETSTANDARD2_0_OR_GREATER
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Common.Extensions
|
||||
{
|
||||
@@ -24,3 +25,4 @@ namespace Tgstation.Server.Common.Extensions
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#if NETSTANDARD2_0_OR_GREATER
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
@@ -105,3 +106,4 @@ namespace Tgstation.Server.Common.Http
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#if NETSTANDARD2_0_OR_GREATER
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
@@ -49,3 +50,4 @@ namespace Tgstation.Server.Common.Http
|
||||
=> httpClient.SendAsync(request, completionOption, cancellationToken);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#if NETSTANDARD2_0_OR_GREATER
|
||||
using System;
|
||||
using System.Net.Http.Headers;
|
||||
|
||||
namespace Tgstation.Server.Common.Http
|
||||
@@ -39,3 +40,4 @@ namespace Tgstation.Server.Common.Http
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
namespace Tgstation.Server.Common.Http
|
||||
#if NETSTANDARD2_0_OR_GREATER
|
||||
namespace Tgstation.Server.Common.Http
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates <see cref="IHttpClient"/>s.
|
||||
@@ -12,3 +13,4 @@
|
||||
IHttpClient CreateClient();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
#if NETSTANDARD2_0_OR_GREATER
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
@@ -31,3 +32,4 @@ namespace Tgstation.Server.Common.Http
|
||||
Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Import Project="../../build/NugetCommon.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(TgsNugetNetFramework)</TargetFrameworks>
|
||||
<TargetFrameworks>$(TgsNugetNetFramework);net2.0</TargetFrameworks>
|
||||
<Version>$(TgsCommonLibraryVersion)</Version>
|
||||
<Nullable>enable</Nullable>
|
||||
<Description>Common functions for tgstation-server.</Description>
|
||||
@@ -10,7 +10,7 @@
|
||||
<PackageReleaseNotes>$(TGS_NUGET_RELEASE_NOTES_COMMON)</PackageReleaseNotes>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'net2.0'">
|
||||
<!-- Usage: ValueTask netstandard backport -->
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
@@ -24,4 +24,11 @@
|
||||
<Message Text="Cleaning icons..." Importance="high" />
|
||||
<Delete Files="../../artifacts/tgs.ico;../../artifacts/tgs.png" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net2.022'">
|
||||
<Compile Remove="Extensions\*.cs" />
|
||||
<Compile Remove="Http\*.cs" />
|
||||
<None Include="Extensions\*.cs" />
|
||||
<None Include="Http\*.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -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}"
|
||||
@@ -448,30 +441,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 +574,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}
|
||||
|
||||
@@ -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>
|
||||
@@ -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="9.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>
|
||||
Reference in New Issue
Block a user