300 lines
10 KiB
C#
300 lines
10 KiB
C#
using System.Diagnostics;
|
|
using System.IO.Compression;
|
|
using System.Net;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Cryptography;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
using Microsoft.Extensions.Logging;
|
|
using Proxd_man.Subscribe;
|
|
|
|
namespace Proxd_man;
|
|
|
|
public class XrayCoreManager
|
|
{
|
|
|
|
private const string XrayLatestReleaseApiUrl = "https://api.github.com/repos/XTLS/Xray-core/releases/latest";
|
|
private const string XrayLatestReleaseAssetDownloadUrlPrefix =
|
|
"https://github.com/XTLS/Xray-core/releases/latest/download/";
|
|
|
|
private readonly ILogger<XrayCoreManager> _logger;
|
|
private readonly string _coreProgramDir;
|
|
private readonly string _workingDirectory;
|
|
|
|
public XrayCoreManager(
|
|
ILogger<XrayCoreManager> logger,
|
|
SubscribeManager subscribeManager,
|
|
string coreProgramDir = "./xray-core",
|
|
string workingDirectory = "./xray-core-run")
|
|
{
|
|
_logger = logger;
|
|
_coreProgramDir = coreProgramDir;
|
|
_workingDirectory = workingDirectory;
|
|
|
|
// TODO: 看看注册订阅更新事件后, 需要进行哪些操作.
|
|
}
|
|
|
|
public string GetXrayCoreProgramPath()
|
|
{
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
{
|
|
return _coreProgramDir + "/xray.exe";
|
|
}
|
|
|
|
return _coreProgramDir + "/xray";
|
|
}
|
|
|
|
public string GetXrayCoreProgramDownloadUrl()
|
|
{
|
|
string os;
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
{
|
|
var osVersion = Environment.OSVersion.Version;
|
|
|
|
switch (osVersion.Major)
|
|
{
|
|
case < 6:
|
|
case 6 when osVersion.Minor < 1:
|
|
throw new Exception("Windows 7 or later is required.");
|
|
case 6 when osVersion.Minor == 1:
|
|
os = "win7";
|
|
break;
|
|
default:
|
|
os = "windows";
|
|
break;
|
|
}
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
{
|
|
os = "linux";
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
|
{
|
|
os = "macos";
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
|
|
{
|
|
os = "freebsd";
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("openbsd")))
|
|
{
|
|
os = "openbsd";
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("Unsupported operating system.");
|
|
}
|
|
|
|
string arch;
|
|
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
|
{
|
|
arch = "64";
|
|
}
|
|
else if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
|
|
{
|
|
arch = "32";
|
|
}
|
|
else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm)
|
|
{
|
|
arch = "arm32-v7a";
|
|
}
|
|
else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
|
{
|
|
arch = "arm64-v8a";
|
|
}
|
|
else if (RuntimeInformation.ProcessArchitecture == Architecture.Armv6)
|
|
{
|
|
arch = "arm32-v6";
|
|
}
|
|
else
|
|
{
|
|
throw new Exception("Unsupported architecture.");
|
|
}
|
|
|
|
return $"{XrayLatestReleaseAssetDownloadUrlPrefix}Xray-{os}-{arch}.zip";
|
|
}
|
|
|
|
private Process CreateXrayCoreProcess(string args, bool redirectOutput = true)
|
|
{
|
|
var programPath = GetXrayCoreProgramPath();
|
|
if (!File.Exists(programPath))
|
|
{
|
|
throw new Exception("Xray-Core program not found: " + programPath);
|
|
}
|
|
|
|
if (!Directory.Exists(_workingDirectory))
|
|
{
|
|
Directory.CreateDirectory(_workingDirectory);
|
|
}
|
|
|
|
return new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = programPath,
|
|
Arguments = args,
|
|
RedirectStandardOutput = redirectOutput,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
WorkingDirectory = _workingDirectory
|
|
}
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取目前已安装的 Xray Core 版本.
|
|
/// </summary>
|
|
/// <returns>如果已安装了 Xray Core 则返回对应的版本号, 否则返回 null.</returns>
|
|
private async Task<string?> GetCurrentXrayCoreVersion()
|
|
{
|
|
// 检查平台, 如果是 Linux, 则检查 coreProgramDir + "/xray", 如果是 Windows, 则检查 coreProgramDir + "/xray.exe"
|
|
var xrayCoreProgramPath = GetXrayCoreProgramPath();
|
|
if (!File.Exists(xrayCoreProgramPath))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var process = CreateXrayCoreProcess("--version");
|
|
process.Start();
|
|
await process.WaitForExitAsync();
|
|
var output = (await process.StandardOutput.ReadToEndAsync()).Trim();
|
|
|
|
var versionMatch = Regex.Match(output, @"Xray (\d+\.\d+\.\d+)");
|
|
return versionMatch.Success ? versionMatch.Groups[1].Value : null;
|
|
}
|
|
|
|
public async Task<string> GetLatestXrayCoreVersion()
|
|
{
|
|
using var client = new HttpClient();
|
|
client.DefaultRequestHeaders.Add("User-Agent", "XrayCoreManager");
|
|
var response = await client.GetAsync(XrayLatestReleaseApiUrl);
|
|
|
|
if (response.StatusCode != HttpStatusCode.OK)
|
|
{
|
|
throw new Exception("Failed to get latest Xray Core version.");
|
|
}
|
|
|
|
using var content = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
|
|
var root = content.RootElement;
|
|
|
|
if (!root.TryGetProperty("tag_name", out var tagNameElement))
|
|
{
|
|
throw new Exception("Failed to get latest Xray Core version.");
|
|
}
|
|
|
|
var tagName = tagNameElement.GetString() ?? throw new Exception("Failed to get latest Xray Core version.");
|
|
return tagName.StartsWith('v') ? tagName[1..] : tagName;
|
|
}
|
|
|
|
public async Task FetchXrayCoreProgram()
|
|
{
|
|
if (!Directory.Exists(_coreProgramDir))
|
|
{
|
|
Directory.CreateDirectory(_coreProgramDir);
|
|
}
|
|
|
|
var programArchiveUrl = GetXrayCoreProgramDownloadUrl();
|
|
var programArchiveDigestUrl = programArchiveUrl + ".dgst";
|
|
_logger.LogDebug("Xray-Core 下载地址: {programArchiveUrl}", programArchiveUrl);
|
|
_logger.LogDebug("Xray-Core 下载地址 (Digest): {programArchiveDigestUrl}", programArchiveDigestUrl);
|
|
|
|
var tempArchiveFile = Path.GetTempFileName();
|
|
|
|
using var httpClient = new HttpClient();
|
|
|
|
_logger.LogDebug("正在下载 Xray-Core...");
|
|
await using var archiveStream = await httpClient.GetStreamAsync(programArchiveUrl);
|
|
await using (var programArchiveFileStream = new FileStream(tempArchiveFile, FileMode.Create, FileAccess.Write))
|
|
{
|
|
await archiveStream.CopyToAsync(programArchiveFileStream);
|
|
}
|
|
|
|
_logger.LogDebug("正在获取 SHA-256 Digest...");
|
|
var digests = await httpClient.GetStringAsync(programArchiveDigestUrl);
|
|
_logger.LogDebug("可用的 Digests: \n{digests}", digests);
|
|
|
|
// digests 每行都有一个不同算法的 digest HEX 值, 因此从 digests 读出开头为 SHA-256= 的行, 并提取出其右侧的 Sha256 值
|
|
var sha256Digest =
|
|
digests.Split('\n').FirstOrDefault(line => line.StartsWith("SHA2-256="))?["SHA2-256=".Length..]
|
|
?.Trim();
|
|
if (sha256Digest == null)
|
|
{
|
|
throw new Exception("Failed to get SHA-256 digest.");
|
|
}
|
|
|
|
_logger.LogDebug("SHA-256 Digest: {sha256Digest}", sha256Digest);
|
|
// Verify digest
|
|
using var sha256 = SHA256.Create();
|
|
|
|
await using (var localArchiveFileStream = new FileStream(tempArchiveFile, FileMode.Open, FileAccess.Read))
|
|
{
|
|
var localDigest = await sha256.ComputeHashAsync(localArchiveFileStream);
|
|
var localDigestHex = BitConverter.ToString(localDigest).Replace("-", "").ToLower();
|
|
_logger.LogDebug("本地 SHA-256 Digest: {localDigest}", localDigestHex);
|
|
|
|
if (localDigestHex != sha256Digest)
|
|
{
|
|
throw new Exception("SHA-256 digest mismatch.");
|
|
}
|
|
|
|
_logger.LogDebug("SHA-256 Digest 验证通过.");
|
|
}
|
|
|
|
ZipFile.ExtractToDirectory(tempArchiveFile, _coreProgramDir, true);
|
|
|
|
// Set permissions
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ||
|
|
RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
|
|
{
|
|
await Process.Start("chmod", "+x " + GetXrayCoreProgramPath()).WaitForExitAsync();
|
|
}
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
|
{
|
|
await Process.Start("chmod", "+x " + GetXrayCoreProgramPath()).WaitForExitAsync();
|
|
}
|
|
|
|
File.Delete(tempArchiveFile);
|
|
}
|
|
|
|
public async Task InitialXrayCoreProgram()
|
|
{
|
|
_logger.LogInformation("正在检查 Xray-Core 安装情况以及版本...");
|
|
var currentVersion = await GetCurrentXrayCoreVersion();
|
|
_logger.LogDebug("当前 Xray-Core 版本: {currentVersion}", currentVersion);
|
|
var requiredUpdate = false;
|
|
if (currentVersion == null)
|
|
{
|
|
requiredUpdate = true;
|
|
_logger.LogInformation("未找到 Xray-Core, 开始下载最新版本...");
|
|
}
|
|
else
|
|
{
|
|
var latestXrayCoreVersion = await GetLatestXrayCoreVersion();
|
|
if (currentVersion != latestXrayCoreVersion)
|
|
{
|
|
requiredUpdate = true;
|
|
_logger.LogInformation("发现新版本 Xray-Core (Version: {latestVersion}), 开始下载最新版本...",
|
|
latestXrayCoreVersion);
|
|
}
|
|
}
|
|
|
|
if (requiredUpdate)
|
|
{
|
|
await FetchXrayCoreProgram();
|
|
_logger.LogInformation("Xray-Core 更新完成.");
|
|
}
|
|
else
|
|
{
|
|
_logger.LogInformation("Xray-Core 已是最新版本, 无需更新.");
|
|
}
|
|
}
|
|
|
|
public async Task<bool> VerifyXrayCoreConfigFile(string configFilePath)
|
|
{
|
|
var process = CreateXrayCoreProcess($"run -test -config {configFilePath}");
|
|
|
|
process.Start();
|
|
await process.WaitForExitAsync();
|
|
return process.ExitCode == 0;
|
|
}
|
|
} |