Compare commits

..

8 Commits

Author SHA1 Message Date
zznty
422963517f test building linux 2022-11-06 17:36:29 +03:00
zznty
d2ac0e44be correct whitelist for compiler 2022-10-16 02:09:44 +07:00
zznty
c5acf61f7c add auto-updates from github 2022-10-15 15:33:57 +07:00
zznty
197d04a661 fix steamcmd default directory 2022-10-15 15:33:09 +07:00
zznty
99ab7d0eea Merge remote-tracking branch 'pve/master' 2022-10-15 02:19:05 +07:00
zznty
17f97af52f add gslt login option (#515)
(cherry picked from commit c81f139fe6b5de0c9f7a005dc2cbe576f4ca8f67)
2022-10-15 02:09:09 +07:00
zznty
ed7c897bd2 Update README.md 2022-10-14 21:59:18 +03:00
zznty
e4d3c3987f fix ci changelog building 2022-10-14 21:48:43 +03:00
15 changed files with 317 additions and 114 deletions

View File

@@ -8,7 +8,7 @@ on:
jobs:
build:
name: Build
runs-on: windows-latest
runs-on: ubuntu-latest
env:
VERSION: ${{ github.ref_name }}
BUILD_CONFIGURATION: Release
@@ -45,6 +45,8 @@ jobs:
uses: mikepenz/release-changelog-builder-action@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commitMode: true
- name: Create release
id: create_release
uses: actions/create-release@v1
@@ -70,4 +72,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
id: ${{ steps.create_release.outputs.id }}
id: ${{ steps.create_release.outputs.id }}

View File

@@ -12,7 +12,10 @@ Torch is the successor to SE Server Extender and gives server admins the tools t
### Fork Difference
* .NET 6.0 runtime
* Additional options & features
* Optimized in-game scripts (also newer compiler & language versions)
* Better configuration via cli arguments, environment variables or xml config
* Designed to run multiple instance from same install directory without having you to waste disk space
* Mostly compatible with original torch's plugins
### Discord
@@ -21,10 +24,13 @@ If you have any questions or issues please join our [discord](https://discord.gg
### Installation
* Unzip the Torch release into its own directory and run the executable. It will automatically download the SE DS and generate the other necessary files.
- If you already have a DS installed you can unzip the Torch files into the folder that contains the DedicatedServer64 folder.
- If you already have a DS installed you can:
* Unzip the Torch files into the folder that contains the DedicatedServer64 folder.
* Pass path to game files using config parameter `gamePath`
# Building
To build Torch you must first have a complete SE Dedicated installation somewhere. Before you open the solution, run the Setup batch file and enter the path of that installation's DedicatedServer64 folder. The script will make a symlink to that folder so the Torch solution can find the DLL references it needs.
As a regular dotnet project with cli by running `dotnet build Torch.Server/Torch.Server.csproj`, with VS 2022 or higher, with JB Rider or Fleet.
If you have a more enjoyable server experience because of Torch, please consider supporting us on Patreon. (https://www.patreon.com/TorchSE)

View File

@@ -31,6 +31,23 @@ namespace Torch
int FontSize { get; set; }
UGCServiceType UgcServiceType { get; set; }
bool EntityManagerEnabled { get; set; }
string LoginToken { get; set; }
UpdateSource UpdateSource { get; set; }
void Save(string path = null);
}
public class UpdateSource
{
public UpdateSourceType SourceType { get; set; }
public string Url { get; set; }
public string Repository { get; set; }
public string Branch { get; set; }
}
public enum UpdateSourceType
{
Github,
Jenkins
}
}

View File

@@ -18,6 +18,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JorgeSerrano.Json.JsonSnakeCaseNamingPolicy" Version="0.9.0" />
<PackageReference Include="NLog" Version="5.0.4" />
<PackageReference Include="SemanticVersioning" Version="2.0.2" />
<PackageReference Include="SpaceEngineersDedicated.ReferenceAssemblies" Version="1.201.13">

View File

@@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using JorgeSerrano.Json;
using Version = SemanticVersioning.Version;
namespace Torch.API.WebAPI;
public class GithubQuery : IUpdateQuery
{
private readonly HttpClient _client;
public GithubQuery(string url)
{
if (url == null) throw new ArgumentNullException(nameof(url));
_client = new()
{
BaseAddress = new(url),
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher,
DefaultRequestHeaders =
{
{"User-Agent", "TorchAPI"}
}
};
}
public void Dispose()
{
_client?.Dispose();
}
public async Task<UpdateRelease> GetLatestReleaseAsync(string repository, string branch = null)
{
var response = await _client.GetFromJsonAsync<Release>($"/repos/{repository}/releases/latest", new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
PropertyNamingPolicy = new JsonSnakeCaseNamingPolicy()
});
if (response is null)
throw new($"Unable to get latest release for {repository}");
return new(Version.Parse(response.TagName), response.Assets.First(b => b.Name == "torch-server.zip").BrowserDownloadUrl);
}
private record Asset(
string Url,
int Id,
string NodeId,
string Name,
string Label,
Uploader Uploader,
string ContentType,
string State,
int Size,
int DownloadCount,
DateTime CreatedAt,
DateTime UpdatedAt,
string BrowserDownloadUrl
);
private record Author(
string Login,
int Id,
string NodeId,
string AvatarUrl,
string GravatarId,
string Url,
string HtmlUrl,
string FollowersUrl,
string FollowingUrl,
string GistsUrl,
string StarredUrl,
string SubscriptionsUrl,
string OrganizationsUrl,
string ReposUrl,
string EventsUrl,
string ReceivedEventsUrl,
string Type,
bool SiteAdmin
);
private record Release(
string Url,
string AssetsUrl,
string UploadUrl,
string HtmlUrl,
int Id,
Author Author,
string NodeId,
string TagName,
string TargetCommitish,
string Name,
bool Draft,
bool Prerelease,
DateTime CreatedAt,
DateTime PublishedAt,
IReadOnlyList<Asset> Assets,
string TarballUrl,
string ZipballUrl,
string Body
);
private record Uploader(
string Login,
int Id,
string NodeId,
string AvatarUrl,
string GravatarId,
string Url,
string HtmlUrl,
string FollowersUrl,
string FollowingUrl,
string GistsUrl,
string StarredUrl,
string SubscriptionsUrl,
string OrganizationsUrl,
string ReposUrl,
string EventsUrl,
string ReceivedEventsUrl,
string Type,
bool SiteAdmin
);
}

View File

@@ -0,0 +1,9 @@
using System;
using System.Threading.Tasks;
namespace Torch.API.WebAPI;
public interface IUpdateQuery : IDisposable
{
Task<UpdateRelease> GetLatestReleaseAsync(string repository, string branch = null);
}

View File

@@ -1,82 +1,52 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using NLog;
using Torch.API.Utils;
using Version = SemanticVersioning.Version;
namespace Torch.API.WebAPI
{
public class JenkinsQuery
public class JenkinsQuery : IUpdateQuery
{
private const string BRANCH_QUERY = "http://136.243.80.164:2690/job/Torch/job/{0}/" + API_PATH;
private const string ARTIFACT_PATH = "artifact/bin/torch-server.zip";
private const string API_PATH = "api/json";
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private static JenkinsQuery _instance;
public static JenkinsQuery Instance => _instance ??= new JenkinsQuery();
private HttpClient _client;
private JenkinsQuery()
private const string ApiPath = "api/json";
private readonly HttpClient _client;
public JenkinsQuery(string url)
{
_client = new HttpClient();
}
public async Task<Job> GetLatestVersion(string branch)
{
var h = await _client.GetAsync(string.Format(BRANCH_QUERY, branch));
if (!h.IsSuccessStatusCode)
{
Log.Error($"'{branch}' Branch query failed with code {h.StatusCode}");
if(h.StatusCode == HttpStatusCode.NotFound)
Log.Error("This likely means you're trying to update a branch that is not public on Jenkins. Sorry :(");
return null;
}
var branchResponse = await h.Content.ReadFromJsonAsync<BranchResponse>();
if (branchResponse is null)
{
Log.Error("Error reading branch response");
return null;
}
if (url == null) throw new ArgumentNullException(nameof(url));
h = await _client.GetAsync($"{branchResponse.LastStableBuild.Url}{API_PATH}");
if (h.IsSuccessStatusCode)
return await h.Content.ReadFromJsonAsync<Job>();
Log.Error($"Job query failed with code {h.StatusCode}");
return null;
}
public async Task<bool> DownloadRelease(Job job, string path)
{
var h = await _client.GetAsync(job.Url + ARTIFACT_PATH);
if (!h.IsSuccessStatusCode)
_client = new()
{
Log.Error($"Job download failed with code {h.StatusCode}");
return false;
}
var s = await h.Content.ReadAsStreamAsync();
#if !NETFRAMEWORK
await using var fs = new FileStream(path, FileMode.Create);
#else
using var fs = new FileStream(path, FileMode.Create);
#endif
await s.CopyToAsync(fs);
return true;
BaseAddress = new(url)
};
}
public async Task<UpdateRelease> GetLatestReleaseAsync(string repository, string branch = null)
{
branch ??= "master";
var response = await _client.GetFromJsonAsync<BranchResponse>($"/job/{repository}/job/{branch}/{ApiPath}");
if (response is null)
throw new($"Unable to get latest release for {repository}");
var job = await _client.GetFromJsonAsync<Job>(
$"/job/{repository}/job/{branch}/{response.LastBuild.Number}/{ApiPath}");
if (job is null)
throw new($"Unable to get latest release for job {repository}/{response.LastBuild.Number}");
return new(job.Version, job.Url + job.Artifacts.First(b => b.FileName == "torch-server.zip").RelativePath);
}
public void Dispose()
{
_client?.Dispose();
}
}
public record BranchResponse(string Name, string Url, Build LastBuild, Build LastStableBuild);
@@ -84,5 +54,12 @@ namespace Torch.API.WebAPI
public record Build(int Number, string Url);
public record Job(int Number, bool Building, string Description, string Result, string Url,
[property: JsonConverter(typeof(SemanticVersionConverter))] Version Version);
[property: JsonConverter(typeof(SemanticVersionConverter))] Version Version,
IReadOnlyList<Artifact> Artifacts);
public record Artifact(
string DisplayPath,
string FileName,
string RelativePath
);
}

View File

@@ -0,0 +1,5 @@
using SemanticVersioning;
namespace Torch.API.WebAPI;
public record UpdateRelease(Version Version, string ArtifactUrl);

View File

@@ -57,7 +57,7 @@ namespace Torch.Server
#endif
if (!configuration.GetValue("noupdate", false))
RunSteamCmd();
RunSteamCmd(configuration);
var processPid = configuration.GetValue<int>("waitForPid");
if (processPid != 0)
@@ -124,11 +124,12 @@ namespace Torch.Server
}
}
public static void RunSteamCmd()
public static void RunSteamCmd(IConfiguration configuration)
{
var log = LogManager.GetLogger("SteamCMD");
var path = Environment.GetEnvironmentVariable("TORCH_STEAMCMD") ?? Path.GetFullPath(STEAMCMD_DIR);
var path = configuration.GetValue<string>("steamCmdPath") ?? ApplicationContext.Current.TorchDirectory
.CreateSubdirectory(STEAMCMD_DIR).FullName;
if (!Directory.Exists(path))
{
@@ -143,7 +144,8 @@ namespace Torch.Server
log.Info("Downloading SteamCMD.");
using (var client = new HttpClient())
using (var file = File.Create(STEAMCMD_ZIP))
client.GetStreamAsync("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip").Result.CopyTo(file);
using (var stream = client.GetStreamAsync("https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip").Result)
stream.CopyTo(file);
ZipFile.ExtractToDirectory(STEAMCMD_ZIP, path);
File.Delete(STEAMCMD_ZIP);
@@ -159,7 +161,7 @@ namespace Torch.Server
log.Info("Checking for DS updates.");
var steamCmdProc = new ProcessStartInfo(steamCmdExePath)
{
Arguments = string.Format(STEAMCMD_ARGS, Environment.GetEnvironmentVariable("TORCH_GAME_PATH") ?? "../"),
Arguments = string.Format(STEAMCMD_ARGS, configuration.GetValue("gamePath", "../")),
WorkingDirectory = path,
UseShellExecute = false,
RedirectStandardOutput = true,

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using NLog;
using Steamworks;
using Torch.Managers.PatchManager;
using Torch.Utils;
namespace Torch.Patches;
[PatchShim]
public static class SteamLoginPatch
{
private static readonly ILogger Log = LogManager.GetCurrentClassLogger();
[ReflectedMethodInfo(null, "LogOnAnonymous", TypeName = "VRage.Steam.MySteamGameServer, VRage.Steam")]
private static MethodInfo LoginMethod = null!;
public static void Patch(PatchContext context)
{
context.GetPattern(LoginMethod).AddPrefix();
}
private static bool Prefix()
{
#pragma warning disable CS0618
var token = TorchBase.Instance.Config.LoginToken;
#pragma warning restore CS0618
if (string.IsNullOrEmpty(token))
return true;
Log.Info("Logging in to Steam with GSLT");
SteamGameServer.LogOn(token);
return false;
}
}

View File

@@ -45,6 +45,10 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Xml" Version="6.0.0" />
<PackageReference Include="NLog" Version="5.0.4" />
<PackageReference Include="PropertyChanged.Fody" Version="4.0.3" PrivateAssets="all" />
<PackageReference Include="Steamworks.NET" Version="20.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile</IncludeAssets>
</PackageReference>
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Management" Version="6.0.0" />
<PackageReference Include="nulastudio.NetCoreBeauty" Version="1.2.9.3" />

View File

@@ -109,6 +109,16 @@ public class TorchConfig : ViewModel, ITorchConfig
GroupName = "Server")]
public bool EntityManagerEnabled { get; set; } = true;
[Display(Name = "Login Token", Description = "Steam GSLT (can be used if you have dynamic ip)", GroupName = "Server")]
public string LoginToken { get; set; }
public UpdateSource UpdateSource { get; set; } = new()
{
Repository = "PveTeam/Torch",
Url = "https://api.github.com",
SourceType = UpdateSourceType.Github
};
// for backward compatibility
public void Save(string path = null) => Initializer.Instance?.ConfigPersistent?.Save(path);
}

View File

@@ -5,6 +5,7 @@ using System.IO.Compression;
using System.IO.Packaging;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
@@ -21,8 +22,9 @@ namespace Torch.Managers
public class UpdateManager : Manager
{
private readonly Timer _updatePollTimer;
private string _torchDir = new FileInfo(typeof(UpdateManager).Assembly.Location).DirectoryName;
private Logger _log = LogManager.GetCurrentClassLogger();
private readonly string _torchDir = ApplicationContext.Current.TorchDirectory.FullName;
private readonly Logger _log = LogManager.GetCurrentClassLogger();
[Dependency]
private FilesystemManager _fsManager = null!;
@@ -50,26 +52,22 @@ namespace Torch.Managers
try
{
var job = await JenkinsQuery.Instance.GetLatestVersion(Torch.TorchVersion.Build);
if (job == null)
{
_log.Info("Failed to fetch latest version.");
return;
}
var updateSource = Torch.Config.UpdateSource;
if (job.Version > Torch.TorchVersion)
IUpdateQuery source = updateSource.SourceType switch
{
_log.Warn($"Updating Torch from version {Torch.TorchVersion} to version {job.Version}");
var updateName = Path.Combine(_fsManager.TempDirectory, "torchupdate.zip");
//new WebClient().DownloadFile(new Uri(releaseInfo.Item2), updateName);
if (!await JenkinsQuery.Instance.DownloadRelease(job, updateName))
{
_log.Warn("Failed to download new release!");
return;
}
UpdateFromZip(updateName, _torchDir);
File.Delete(updateName);
_log.Warn($"Torch version {job.Version} has been installed, please restart Torch to finish the process.");
UpdateSourceType.Github => new GithubQuery(updateSource.Url),
UpdateSourceType.Jenkins => new JenkinsQuery(updateSource.Url),
_ => throw new ArgumentOutOfRangeException()
};
var release = await source.GetLatestReleaseAsync(updateSource.Repository, updateSource.Branch);
if (release.Version > Torch.TorchVersion)
{
_log.Warn($"Updating Torch from version {Torch.TorchVersion} to version {release.Version}");
await UpdateAsync(release, _torchDir);
_log.Warn($"Torch version {release.Version} has been installed, please restart Torch to finish the process.");
}
else
{
@@ -83,23 +81,29 @@ namespace Torch.Managers
}
}
private void UpdateFromZip(string zipFile, string extractPath)
private async Task UpdateAsync(UpdateRelease release, string extractPath)
{
using (var zip = ZipFile.OpenRead(zipFile))
using var client = new HttpClient();
await using var stream = await client.GetStreamAsync(release.ArtifactUrl);
UpdateFromZip(stream, extractPath);
}
private void UpdateFromZip(Stream zipStream, string extractPath)
{
using var zip = new ZipArchive(zipStream, ZipArchiveMode.Read, true);
foreach (var file in zip.Entries)
{
foreach (var file in zip.Entries)
{
if(file.Name == "NLog-user.config" && File.Exists(Path.Combine(extractPath, file.FullName)))
continue;
if(file.Name == "NLog-user.config" && File.Exists(Path.Combine(extractPath, file.FullName)))
continue;
_log.Debug($"Unzipping {file.FullName}");
var targetFile = Path.Combine(extractPath, file.FullName);
_fsManager.SoftDelete(extractPath, file.FullName);
file.ExtractToFile(targetFile, true);
}
//zip.ExtractToDirectory(extractPath); //throws exceptions sometimes?
_log.Debug($"Unzipping {file.FullName}");
var targetFile = Path.Combine(extractPath, file.FullName);
_fsManager.SoftDelete(extractPath, file.FullName);
file.ExtractToFile(targetFile, true);
}
//zip.ExtractToDirectory(extractPath); //throws exceptions sometimes?
}
/// <inheritdoc />

View File

@@ -12,6 +12,7 @@ using System.Reflection.Emit;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.CodeAnalysis;
using ParallelTasks;
using ProtoBuf;
using Torch.Managers.PatchManager;
using Torch.Managers.PatchManager.MSIL;
@@ -82,6 +83,14 @@ namespace Torch.Patches
using var batch = MyScriptCompiler.Static.Whitelist.OpenBatch();
batch.AllowTypes(MyWhitelistTarget.ModApi, typeof(ConcurrentQueue<>));
batch.AllowNamespaceOfTypes(MyWhitelistTarget.Both, typeof(ImmutableArray), typeof(ArrayExtensions));
batch.AllowTypes(MyWhitelistTarget.ModApi, typeof(ProtoContractAttribute).Assembly.GetExportedTypes()
.Where(b => b.Namespace == "ProtoBuf" && b.Name.Contains("Attribute"))
.Concat(new[]
{
typeof(DataFormat), typeof(MemberSerializationOptions), typeof(ImplicitFields)
}).ToArray());
batch.AllowTypes(MyWhitelistTarget.ModApi, typeof(WorkData));
return false;
}

View File

@@ -104,14 +104,8 @@ namespace Torch
#pragma warning restore CS0618
Config = config;
var versionString = GetType().Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!
.InformationalVersion;
if (!SemanticVersioning.Version.TryParse(versionString, out var version))
throw new TypeLoadException("Unable to parse the Torch version from the assembly.");
TorchVersion = version;
var assemblyVersion = GetType().Assembly.GetName().Version ?? new Version();
TorchVersion = new(assemblyVersion.Major, assemblyVersion.Minor, assemblyVersion.Build);
RunArgs = Array.Empty<string>();
@@ -129,7 +123,7 @@ namespace Torch
Managers.AddManager(sessionManager);
Managers.AddManager(new PatchManager(this));
Managers.AddManager(new FilesystemManager(this));
// Managers.AddManager(new UpdateManager(this));
Managers.AddManager(new UpdateManager(this));
Managers.AddManager(new EventManager(this));
#pragma warning disable CS0618
Managers.AddManager(Plugins);