修复虚拟网卡添加路由错误,和增加自动更新

This commit is contained in:
snltty
2024-07-17 20:31:04 +08:00
parent 2a610e1a46
commit 92d0405a70
39 changed files with 635 additions and 749 deletions

View File

@@ -48,16 +48,18 @@ jobs:
env: env:
GITHUB_TOKEN: '${{ secrets.ACTIONS_TOKEN }}' GITHUB_TOKEN: '${{ secrets.ACTIONS_TOKEN }}'
with: with:
tag_name: v1.1.1.3 tag_name: v1.1.2.1
release_name: v1.1.1.3.${{ steps.date.outputs.today }} release_name: v1.1.2.1.${{ steps.date.outputs.today }}
draft: false draft: false
prerelease: false prerelease: false
body: | body: |
1. 更新配置同步方式,以版本同步 1. 更新配置同步方式,以版本同步
2. 增加设备列表搜索,按 设备名/设备IP/虚拟网卡IP/端口转发端口和IP 搜索 2. 设备列表搜索,按 设备名/设备IP/虚拟网卡IP/端口转发端口和IP 搜索
3. 新增端口转发配置复制可以将A转发到B的配置复制给C转发到B 3. 端口转发配置复制可以将A转发到B的配置复制给C转发到B
4. 新增服务器代理穿透配置复制 4. 服务器代理穿透配置复制
5. 请更新服务端 5. 修复虚拟网卡添加路由错误
6. 自动更新,管理所有客户端更新
7. 请更新服务端
- name: upload win x64 - name: upload win x64
id: upload-win-x64 id: upload-win-x64

View File

@@ -36,5 +36,5 @@ jobs:
- name: Push - name: Push
run: | run: |
nuget push ./linker.tunnel/bin/release/linker.tunnel.1.1.1.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -ApiKey ${{ secrets.NUGET_KEY }} -NoSymbol nuget push ./linker.tunnel/bin/release/linker.tunnel.1.1.2.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -ApiKey ${{ secrets.NUGET_KEY }} -NoSymbol
nuget push ./linker.libs/bin/release/linker.libs.1.1.1.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -ApiKey ${{ secrets.NUGET_KEY }} -NoSymbol nuget push ./linker.libs/bin/release/linker.libs.1.1.2.nupkg -Source https://api.nuget.org/v3/index.json -SkipDuplicate -ApiKey ${{ secrets.NUGET_KEY }} -NoSymbol

View File

@@ -1,53 +0,0 @@
version: '1.0'
name: branch-pipeline
displayName: BranchPipeline
stages:
- stage:
name: compile
displayName: 编译
steps:
- step: build@maven
name: build_maven
displayName: Maven 构建
# 支持6、7、8、9、10、11六个版本
jdkVersion: 8
# 支持2.2.1、3.2.5、3.3.9、3.5.2、3.5.3、3.5.4、3.6.1、3.6.3八个版本
mavenVersion: 3.3.9
# 构建命令
commands:
- mvn -B clean package -Dmaven.test.skip=true
# 非必填字段开启后表示将构建产物暂存但不会上传到制品库中7天后自动清除
artifacts:
# 构建产物名字作为产物的唯一标识可向下传递支持自定义默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址
- name: BUILD_ARTIFACT
# 构建产物获取路径是指代码编译完毕之后构建物的所在路径如通常jar包在target目录下。当前目录为代码库根目录
path:
- ./target
- step: publish@general_artifacts
name: publish_general_artifacts
displayName: 上传制品
# 上游构建任务定义的产物名默认BUILD_ARTIFACT
dependArtifact: BUILD_ARTIFACT
# 上传到制品库时的制品命名默认output
artifactName: output
dependsOn: build_maven
- stage:
name: release
displayName: 发布
steps:
- step: publish@release_artifacts
name: publish_release_artifacts
displayName: '发布'
# 上游上传制品任务的产出
dependArtifact: output
# 发布制品版本号
version: '1.0.0.0'
# 是否开启版本号自增,默认开启
autoIncrement: true
triggers:
push:
branches:
exclude:
- master
include:
- .*

View File

@@ -1,51 +0,0 @@
version: '1.0'
name: master-pipeline
displayName: MasterPipeline
stages:
- stage:
name: compile
displayName: 编译
steps:
- step: build@maven
name: build_maven
displayName: Maven 构建
# 支持6、7、8、9、10、11六个版本
jdkVersion: 8
# 支持2.2.1、3.2.5、3.3.9、3.5.2、3.5.3、3.5.4、3.6.1、3.6.3八个版本
mavenVersion: 3.3.9
# 构建命令
commands:
- mvn -B clean package -Dmaven.test.skip=true
# 非必填字段开启后表示将构建产物暂存但不会上传到制品库中7天后自动清除
artifacts:
# 构建产物名字作为产物的唯一标识可向下传递支持自定义默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址
- name: BUILD_ARTIFACT
# 构建产物获取路径是指代码编译完毕之后构建物的所在路径如通常jar包在target目录下。当前目录为代码库根目录
path:
- ./target
- step: publish@general_artifacts
name: publish_general_artifacts
displayName: 上传制品
# 上游构建任务定义的产物名默认BUILD_ARTIFACT
dependArtifact: BUILD_ARTIFACT
# 上传到制品库时的制品命名默认output
artifactName: output
dependsOn: build_maven
- stage:
name: release
displayName: 发布
steps:
- step: publish@release_artifacts
name: publish_release_artifacts
displayName: '发布'
# 上游上传制品任务的产出
dependArtifact: output
# 发布制品版本号
version: '1.0.0.0'
# 是否开启版本号自增,默认开启
autoIncrement: true
triggers:
push:
branches:
include:
- master

View File

@@ -1,40 +0,0 @@
version: '1.0'
name: pr-pipeline
displayName: PRPipeline
stages:
- stage:
name: compile
displayName: 编译
steps:
- step: build@maven
name: build_maven
displayName: Maven 构建
# 支持6、7、8、9、10、11六个版本
jdkVersion: 8
# 支持2.2.1、3.2.5、3.3.9、3.5.2、3.5.3、3.5.4、3.6.1、3.6.3八个版本
mavenVersion: 3.3.9
# 构建命令
commands:
- mvn -B clean package -Dmaven.test.skip=true
# 非必填字段开启后表示将构建产物暂存但不会上传到制品库中7天后自动清除
artifacts:
# 构建产物名字作为产物的唯一标识可向下传递支持自定义默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址
- name: BUILD_ARTIFACT
# 构建产物获取路径是指代码编译完毕之后构建物的所在路径如通常jar包在target目录下。当前目录为代码库根目录
path:
- ./target
- step: publish@general_artifacts
name: publish_general_artifacts
displayName: 上传制品
# 上游构建任务定义的产物名默认BUILD_ARTIFACT
dependArtifact: BUILD_ARTIFACT
# 构建产物制品库默认default系统默认创建
artifactRepository: default
# 上传到制品库时的制品命名默认output
artifactName: output
dependsOn: build_maven
triggers:
pr:
branches:
include:
- master

View File

@@ -0,0 +1,13 @@
---
sidebar_position: 6
---
# 6、发布
你可以自己发布项目,因为涉及到很多内容,所以建议使用脚本发布
1. 安装 <a href="https://nodejs.org/en/download/package-manager">Nodejs https://nodejs.org/en/download/package-manager</a>
2. 安装 <a href="https://dotnet.microsoft.com/zh-cn/download">.NET8.0 SDK https://dotnet.microsoft.com/zh-cn/download</a> 或者安装 <a href="https://visualstudio.microsoft.com/zh-hans/vs/">vs2022 https://visualstudio.microsoft.com/zh-hans/vs/</a>
3. 发布项目自动压缩,所以你需要安装 <a href="https://www.7-zip.org/">7zip https://www.7-zip.org/</a>
4.`cmd`或者`PowerShell`下运行根目录下`publish.bat`,等待发布完成
5. 发布完成后,在根目录下,`public`>`publish``public`>`publish-zip`

View File

@@ -1,8 +1,8 @@
--- ---
sidebar_position: 6 sidebar_position: 7
--- ---
# 6、集成打洞到你的项目 # 7、集成打洞到你的项目
你需要自己实现信标服务器,用于交换打洞信息 你需要自己实现信标服务器,用于交换打洞信息

View File

@@ -14,9 +14,9 @@
<Copyright>snltty</Copyright> <Copyright>snltty</Copyright>
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl> <PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl> <RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>
<Version>1.1.1</Version> <Version>1.1.2</Version>
<AssemblyVersion>1.1.1.3</AssemblyVersion> <AssemblyVersion>1.1.2.1</AssemblyVersion>
<FileVersion>1.1.1.3</FileVersion> <FileVersion>1.1.2.1</FileVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType> <DebugType>full</DebugType>

View File

@@ -14,7 +14,7 @@
<PublishAot>false</PublishAot> <PublishAot>false</PublishAot>
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault> <JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
<EnablePreviewFeatures>true</EnablePreviewFeatures> <EnablePreviewFeatures>true</EnablePreviewFeatures>
<Version>1.1.1</Version> <Version>1.1.2</Version>
<Authors>snltty</Authors> <Authors>snltty</Authors>
<Company>snltty</Company> <Company>snltty</Company>
<Description>snltty</Description> <Description>snltty</Description>
@@ -22,8 +22,8 @@
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl> <PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl> <RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>
<PackageReleaseNotes>snltty service</PackageReleaseNotes> <PackageReleaseNotes>snltty service</PackageReleaseNotes>
<AssemblyVersion>1.1.1.3</AssemblyVersion> <AssemblyVersion>1.1.2.1</AssemblyVersion>
<FileVersion>1.1.1.3</FileVersion> <FileVersion>1.1.2.1</FileVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@@ -13,8 +13,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.service", "linker.se
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.tunnel", "linker.tunnel\linker.tunnel.csproj", "{AFADE8D6-AB00-456B-9F43-53BC95B7B608}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "linker.tunnel", "linker.tunnel\linker.tunnel.csproj", "{AFADE8D6-AB00-456B-9F43-53BC95B7B608}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "linker.updater", "linker.updater\linker.updater.csproj", "{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -85,18 +83,6 @@ Global
{AFADE8D6-AB00-456B-9F43-53BC95B7B608}.Release|x64.Build.0 = Release|Any CPU {AFADE8D6-AB00-456B-9F43-53BC95B7B608}.Release|x64.Build.0 = Release|Any CPU
{AFADE8D6-AB00-456B-9F43-53BC95B7B608}.Release|x86.ActiveCfg = Release|Any CPU {AFADE8D6-AB00-456B-9F43-53BC95B7B608}.Release|x86.ActiveCfg = Release|Any CPU
{AFADE8D6-AB00-456B-9F43-53BC95B7B608}.Release|x86.Build.0 = Release|Any CPU {AFADE8D6-AB00-456B-9F43-53BC95B7B608}.Release|x86.Build.0 = Release|Any CPU
{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}.Debug|x64.ActiveCfg = Debug|Any CPU
{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}.Debug|x64.Build.0 = Debug|Any CPU
{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}.Debug|x86.ActiveCfg = Debug|Any CPU
{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}.Debug|x86.Build.0 = Debug|Any CPU
{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}.Release|Any CPU.Build.0 = Release|Any CPU
{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}.Release|x64.ActiveCfg = Release|Any CPU
{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}.Release|x64.Build.0 = Release|Any CPU
{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}.Release|x86.ActiveCfg = Release|Any CPU
{B7E2B873-C96E-4F3E-9411-D8D549F4D3A5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@@ -9,7 +9,7 @@
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault> <JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
<EnablePreviewFeatures>true</EnablePreviewFeatures> <EnablePreviewFeatures>true</EnablePreviewFeatures>
<Title>linker tunnel</Title> <Title>linker tunnel</Title>
<Version>1.1.1</Version> <Version>1.1.2</Version>
<Authors>snltty</Authors> <Authors>snltty</Authors>
<Company>snltty</Company> <Company>snltty</Company>
<Description>linker tunnel</Description> <Description>linker tunnel</Description>
@@ -17,8 +17,8 @@
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl> <PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl> <RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>
<PackageReleaseNotes>linker tunnel</PackageReleaseNotes> <PackageReleaseNotes>linker tunnel</PackageReleaseNotes>
<AssemblyVersion>1.1.1.3</AssemblyVersion> <AssemblyVersion>1.1.2.1</AssemblyVersion>
<FileVersion>1.1.1.3</FileVersion> <FileVersion>1.1.2.1</FileVersion>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@@ -1,256 +0,0 @@
using Fizzler.Systems.HtmlAgilityPack;
using HtmlAgilityPack;
using linker.libs;
using System.Diagnostics;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text;
namespace linker.updater
{
internal class Program
{
static async Task Main(string[] args)
{
if (args.Length > 0)
{
rootPath = args[0];
}
Updater();
await Helper.Await();
}
static string rootPath = "./updater";
static void Updater()
{
Task.Factory.StartNew(async () =>
{
while (true)
{
try
{
UpdateInfo updateInfo = GetUpdateInfo();
if (updateInfo != null)
{
if (NeedDownload())
{
await DownloadUpdate(updateInfo);
}
}
}
catch (Exception)
{
}
try
{
if (NeedExtract())
{
ExtractUpdate();
}
}
catch (Exception)
{
}
await Task.Delay(15000);
}
}, TaskCreationOptions.LongRunning);
}
static bool NeedExtract()
{
try
{
return File.Exists(Path.Join(rootPath, "updater.zip")) && File.Exists(Path.Join(rootPath, "extract.txt"));
}
catch (Exception)
{
}
return false;
}
static void ExtractUpdate()
{
try
{
string[] command = File.ReadAllText(Path.Join(rootPath, "extract.txt")).Split(Environment.NewLine);
CommandHelper.Execute(string.Empty, new string[] { command[0] });
ZipFile.ExtractToDirectory(Path.Join(rootPath, "updater.zip"), Path.Join(rootPath, "../"), Encoding.UTF8, true);
File.Delete(Path.Join(rootPath, "extract.txt"));
File.Delete(Path.Join(rootPath, "updater.zip"));
CommandHelper.Execute(string.Empty, new string[] { command[1] });
}
catch (Exception)
{
}
}
static bool NeedDownload()
{
try
{
return File.Exists(Path.Join(rootPath, "extract.txt"));
}
catch (Exception)
{
}
return false;
}
static async Task DownloadUpdate(UpdateInfo updateInfo)
{
try
{
if (Directory.Exists(rootPath) == false)
{
Directory.CreateDirectory(rootPath);
}
using FileStream fileStream = new FileStream(Path.Join(rootPath, "updater.zip"), FileMode.OpenOrCreate, FileAccess.ReadWrite);
using HttpClient httpClient = new HttpClient();
using Stream stream = await httpClient.GetStreamAsync(updateInfo.Url);
await stream.CopyToAsync(fileStream);
fileStream.Flush();
fileStream.Close();
fileStream.Dispose();
}
catch (Exception)
{
}
}
static UpdateInfo GetUpdateInfo()
{
try
{
using HttpClient httpClient = new HttpClient();
string str = httpClient.GetStringAsync("http://gh.snltty.com:1808/https://github.com/snltty/linker/releases/latest").Result;
HtmlDocument hdc = new HtmlDocument();
hdc.LoadHtml(str);
string tag = hdc.DocumentNode.QuerySelector("span.css-truncate-target span").InnerText.Trim();
str = httpClient.GetStringAsync($"http://gh.snltty.com:1808/https://github.com/snltty/linker/releases/expanded_assets/{tag}").Result;
HtmlDocument hdc1 = new HtmlDocument();
hdc1.LoadHtml(str);
string msg = hdc.DocumentNode.QuerySelector(".markdown-body").InnerText.Trim();
string system = OperatingSystem.IsWindows() ? "win" : OperatingSystem.IsLinux() ? "linux" : "osx";
string arch = RuntimeInformation.ProcessArchitecture.ToString().ToLower();
string zip = $"linker-{system}-{arch}.zip";
var a = hdc1.DocumentNode.QuerySelectorAll("a").FirstOrDefault(c => c.InnerText.Trim() == zip);
File.WriteAllText(Path.Join(rootPath, "version.txt"), tag);
File.WriteAllText(Path.Join(rootPath, "msg.txt"), msg);
return new UpdateInfo
{
Msg = msg,
Version = tag,
Url = $"http://gh.snltty.com:1808/https://github.com{a.GetAttributeValue("href", "").Trim()}"
};
}
catch (Exception)
{
}
return null;
}
sealed class UpdateInfo
{
public string Version { get; set; }
public string Msg { get; set; }
public string Url { get; set; }
}
}
public sealed class CommandHelper
{
public static string Execute(string arg, string[] commands, bool readResult = true)
{
if (OperatingSystem.IsWindows())
{
return Windows(arg, commands, readResult);
}
else if (OperatingSystem.IsLinux())
{
return Linux(arg, commands, readResult);
}
return Osx(arg, commands, readResult);
}
public static string Windows(string arg, string[] commands, bool readResult = true)
{
return Execute("cmd.exe", arg, commands, readResult);
}
public static string Linux(string arg, string[] commands, bool readResult = true)
{
return Execute("/bin/bash", arg, commands, readResult);
}
public static string Osx(string arg, string[] commands, bool readResult = true)
{
return Execute("/bin/bash", arg, commands, readResult);
}
public static Process Execute(string fileName, string arg)
{
Process proc = new Process();
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.FileName = fileName;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.RedirectStandardInput = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.Arguments = arg;
proc.StartInfo.Verb = "runas";
proc.Start();
//Process proc = Process.Start(fileName, arg);
return proc;
}
public static string Execute(string fileName, string arg, string[] commands, bool readResult = true)
{
using Process proc = new Process();
proc.StartInfo.WorkingDirectory = Path.GetFullPath(Path.Join("./"));
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.FileName = fileName;
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.RedirectStandardInput = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.Arguments = arg;
proc.StartInfo.Verb = "runas";
proc.Start();
if (commands.Length > 0)
{
for (int i = 0; i < commands.Length; i++)
{
proc.StandardInput.WriteLine(commands[i]);
}
}
proc.StandardInput.AutoFlush = true;
if (readResult)
{
proc.StandardInput.WriteLine("exit");
proc.StandardInput.Close();
string output = proc.StandardOutput.ReadToEnd();
string error = proc.StandardError.ReadToEnd();
proc.WaitForExit();
proc.Close();
proc.Dispose();
return output;
}
proc.StandardOutput.Read();
proc.Close();
proc.Dispose();
return string.Empty;
}
}
}

View File

@@ -1,79 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC 清单选项
如果想要更改 Windows 用户帐户控制级别,请使用
以下节点之一替换 requestedExecutionLevel 节点。
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
指定 requestedExecutionLevel 元素将禁用文件和注册表虚拟化。
如果你的应用程序需要此虚拟化来实现向后兼容性,则移除此
元素。
-->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- 设计此应用程序与其一起工作且已针对此应用程序进行测试的
Windows 版本的列表。取消评论适当的元素,
Windows 将自动选择最兼容的环境。 -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- 指示该应用程序可感知 DPI 且 Windows 在 DPI 较高时将不会对其进行
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI无需
选择加入。选择加入此设置的 Windows 窗体应用程序(面向 .NET Framework 4.6)还应
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。
将应用程序设为感知长路径。请参阅 https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
-->
<!-- 启用 Windows 公共控件和对话框的主题(Windows XP 和更高版本) -->
<!--
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
-->
</assembly>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1,50 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>disable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<Configurations>Debug;Release</Configurations>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<PublishAot>false</PublishAot>
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
<EnablePreviewFeatures>true</EnablePreviewFeatures>
<Version>1.1.1</Version>
<Authors>snltty</Authors>
<Company>snltty</Company>
<Description>snltty</Description>
<Copyright>snltty</Copyright>
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>
<PackageReleaseNotes>snltty updater</PackageReleaseNotes>
<AssemblyVersion>1.1.1.3</AssemblyVersion>
<FileVersion>1.1.1.3</FileVersion>
<ApplicationIcon>favicon.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>full</DebugType>
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<Optimize>True</Optimize>
</PropertyGroup>
<ItemGroup>
<Content Include="favicon.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fizzler.Systems.HtmlAgilityPack" Version="1.2.1" />
<PackageReference Include="System.IO.Compression.ZipFile" Version="4.3.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\linker.libs\linker.libs.csproj" />
</ItemGroup>
</Project>

View File

@@ -3,4 +3,10 @@ import { sendWebsocketMsg } from './request'
export const getUpdater = () => { export const getUpdater = () => {
return sendWebsocketMsg('updaterclient/get'); return sendWebsocketMsg('updaterclient/get');
}
export const confirm = (machineId) => {
return sendWebsocketMsg('updaterclient/confirm', machineId);
}
export const exit = (machineId) => {
return sendWebsocketMsg('updaterclient/exit', machineId);
} }

View File

@@ -50,9 +50,9 @@ export default {
steps:['选择模式','服务端','客户端','完成'] steps:['选择模式','服务端','客户端','完成']
}); });
watch(() => globalData.value.configed, (val) => { watch(() => globalData.value.config.configed, (val) => {
if (val) { if (val) {
state.show = globalData.value.connected && globalData.value.configed && globalData.value.config.Common.Install == false; state.show = globalData.value.api.connected && globalData.value.config.configed && globalData.value.config.Common.Install == false;
} }
}); });

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="status-api-wrap" :class="{connected:connected}"> <div class="status-api-wrap" :class="{connected:connected}">
<el-popconfirm confirm-button-text="清除" cancel-button-text="更改" title="确定你的惭怍" @cancel="handleShow" @confirm="handleResetConnect" > <el-popconfirm confirm-button-text="清除" cancel-button-text="更改" title="确定你的操作" @cancel="handleShow" @confirm="handleResetConnect" >
<template #reference> <template #reference>
<a href="javascript:;" > <a href="javascript:;" >
<el-icon size="16"><Tools /></el-icon> <el-icon size="16"><Tools /></el-icon>
@@ -32,29 +32,28 @@ import { initWebsocket, subWebsocketState,closeWebsocket } from '../../apis/requ
import { getSignInfo } from '../../apis/signin' import { getSignInfo } from '../../apis/signin'
import { getConfig } from '../../apis/config' import { getConfig } from '../../apis/config'
import {Tools} from '@element-plus/icons-vue' import {Tools} from '@element-plus/icons-vue'
import { getUpdater } from '@/apis/updater';
export default { export default {
components:{Tools}, components:{Tools},
setup(props) { setup(props) {
const globalData = injectGlobalData(); const globalData = injectGlobalData();
const connected = computed(()=>globalData.value.connected); const connected = computed(()=>globalData.value.api.connected);
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const defaultInfo = {api:`${window.location.hostname}:1803`,psd:'snltty'}; const defaultInfo = {api:`${window.location.hostname}:1803`,psd:'snltty'};
const handleResetConnect = () => {
localStorage.setItem('api-cache', '');
router.push({name:route.name});
window.location.reload();
}
const queryCache = JSON.parse(localStorage.getItem('api-cache') || JSON.stringify(defaultInfo)); const queryCache = JSON.parse(localStorage.getItem('api-cache') || JSON.stringify(defaultInfo));
const state = reactive({ const state = reactive({
api:queryCache.api, api:queryCache.api,
psd:queryCache.psd, psd:queryCache.psd,
showPort: false showPort: false
}); });
const showPort = computed(() => globalData.value.connected == false && state.showPort); const showPort = computed(() => globalData.value.api.connected == false && state.showPort);
const handleResetConnect = () => {
localStorage.setItem('api-cache', '');
router.push({name:route.name});
window.location.reload();
}
const handleConnect = () => { const handleConnect = () => {
queryCache.api = state.api; queryCache.api = state.api;
queryCache.psd = state.psd; queryCache.psd = state.psd;
@@ -78,7 +77,7 @@ export default {
globalData.value.config.Client = res.Client; globalData.value.config.Client = res.Client;
globalData.value.config.Server = res.Server; globalData.value.config.Server = res.Server;
globalData.value.config.Running = res.Running; globalData.value.config.Running = res.Running;
globalData.value.configed = true; globalData.value.config.configed = true;
setTimeout(()=>{ setTimeout(()=>{
_getConfig(); _getConfig();
},1000); },1000);
@@ -102,21 +101,12 @@ export default {
},1000); },1000);
}); });
} }
const _getUpdater = ()=>{
getUpdater().then((res)=>{
if(res){
globalData.value.updater.Version = res.Version;
globalData.value.updater.Msg = res.Msg;
}
}).catch((err)=>{});
}
onMounted(() => { onMounted(() => {
setTimeout(() => { state.showPort = true; }, 100); setTimeout(() => { state.showPort = true; }, 500);
subWebsocketState((state) => { if (state) { subWebsocketState((state) => { if (state) {
_getConfig(); _getConfig();
_getSignInfoInfo(); _getSignInfoInfo();
_getUpdater();
}}); }});
router.isReady().then(()=>{ router.isReady().then(()=>{
state.api = route.query.api ?`${window.location.hostname}:${route.query.api}` : state.api; state.api = route.query.api ?`${window.location.hostname}:${route.query.api}` : state.api;

View File

@@ -63,7 +63,6 @@ export default {
state.loading = false; state.loading = false;
state.show = false; state.show = false;
ElMessage.success('已操作'); ElMessage.success('已操作');
globalData.value.updateFlag = Date.now();
}).catch((err) => { }).catch((err) => {
state.loading = false; state.loading = false;
ElMessage.success('操作失败!'); ElMessage.success('操作失败!');

View File

@@ -1,22 +1,19 @@
import { subWebsocketState } from "@/apis/request"; import { subWebsocketState } from "@/apis/request";
import { computed, inject, provide, ref } from "vue"; import { inject, provide, ref } from "vue";
const globalDataSymbol = Symbol(); const globalDataSymbol = Symbol();
export const provideGlobalData = () => { export const provideGlobalData = () => {
const globalData = ref({ const globalData = ref({
//已连接 //已连接
connected: false, api: { connected: false },
updateFlag: false,
height: 0, height: 0,
config: { Common: {}, Client: {}, Server: {}, Running: {} }, config: { Common: {}, Client: {}, Server: {}, Running: {}, configed: false },
configed: false,
signin: { Connected: false, Connecting: false, Version: 'v1.0.0.0' }, signin: { Connected: false, Connecting: false, Version: 'v1.0.0.0' },
bufferSize: ['1KB', '2KB', '4KB', '8KB', '16KB', '32KB', '64KB', '128KB', '256KB', '512KB', '1024KB'], bufferSize: ['1KB', '2KB', '4KB', '8KB', '16KB', '32KB', '64KB', '128KB', '256KB', '512KB', '1024KB']
updater: { Msg: '', Version: '' }
}); });
subWebsocketState((state) => { subWebsocketState((state) => {
globalData.value.connected = state; globalData.value.api.connected = state;
}); });
provide(globalDataSymbol, globalData); provide(globalDataSymbol, globalData);

View File

@@ -18,16 +18,29 @@
<p class="flex"> <p class="flex">
<span>{{ scope.row.IP }}</span> <span>{{ scope.row.IP }}</span>
<span class="flex-1"></span> <span class="flex-1"></span>
<a href="javascript:;" class="download" title="下载更新" @click="handleUpdate"> <a href="javascript:;" class="download" title="下载更新" @click="handleUpdate(scope.row)">
<template v-if="scope.row.Version != version && scope.row.Version == updater.Version"> <span :title="updateText(scope.row)" :class="updateColor(scope.row)">
<span title="与服务器版本不一致,建议更新">{{scope.row.Version}}<el-icon size="14"><Download /></el-icon></span> <span>{{scope.row.Version}}</span>
</template> <template v-if="updater.list[scope.row.MachineId]">
<template v-else-if="scope.row.Version != updater.Version"> <template v-if="updater.list[scope.row.MachineId].Status == 1">
<span title="不是最新版本,建议更新">{{scope.row.Version}}<el-icon size="14"><Download /></el-icon></span> <el-icon size="14" class="loading"><Loading /></el-icon>
</template> </template>
<template v-else> <template v-else-if="updater.list[scope.row.MachineId].Status == 2">
<span title="版本一致,但我无法阻止你喜欢更新" class="green">{{scope.row.Version}}</span> <el-icon size="14"><Download /></el-icon>
</template> </template>
<template v-else-if="updater.list[scope.row.MachineId].Status == 3 || updater.list[scope.row.MachineId].Status == 5">
<el-icon size="14" class="loading"><Loading /></el-icon>
<span class="progress" v-if="updater.list[scope.row.MachineId].Length ==0">0%</span>
<span class="progress" v-else>{{parseInt(updater.list[scope.row.MachineId].Current/updater.list[scope.row.MachineId].Length*100)}}%</span>
</template>
<template v-else-if="updater.list[scope.row.MachineId].Status == 6">
<el-icon size="14" class="green"><CircleCheck /></el-icon>
</template>
</template>
<template v-else>
<el-icon size="14"><Download /></el-icon>
</template>
</span>
</a> </a>
</p> </p>
</div> </div>
@@ -37,51 +50,124 @@
<script> <script>
import { injectGlobalData } from '@/provide'; import { injectGlobalData } from '@/provide';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import {WarnTriangleFilled,StarFilled,Search,Download} from '@element-plus/icons-vue' import {StarFilled,Search,Download,Loading,CircleCheck} from '@element-plus/icons-vue'
import { ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { confirm, exit } from '@/apis/updater';
import { useUpdater } from './updater';
export default { export default {
emits:['edit','refresh'], emits:['edit','refresh'],
components:{WarnTriangleFilled,StarFilled,Search,Download}, components:{StarFilled,Search,Download,Loading,CircleCheck},
setup(props,{emit}) { setup(props,{emit}) {
const globalData = injectGlobalData(); const globalData = injectGlobalData();
const version = computed(()=>globalData.value.signin.Version); const version = computed(()=>globalData.value.signin.Version);
const updater = computed(()=>globalData.value.updater); const name = ref(sessionStorage.getItem('search-name') || '');
const name = ref('') const updater = useUpdater();
const updateText = (row)=>{
if(!updater.value.list[row.MachineId]){
return '未检测到更新';
}
if(updater.value.list[row.MachineId].Status <= 2) {
return row.Version != version.value
? '与服务器版本不一致,建议更新'
: updater.value.list[row.MachineId].Version != row.Version
? '不是最新版本,建议更新' : '版本一致,但我无法阻止你喜欢更新'
}
return {
3:'正在下载',
4:'已下载',
5:'正在解压',
6:'已解压,请重启',
}[updater.value.list[row.MachineId].Status];
}
const updateColor = (row)=>{
return row.Version != version.value
? 'red'
: updater.value.list[row.MachineId] && updater.value.list[row.MachineId].Version != row.Version
? 'yellow' :'green'
}
const handleEdit = (row)=>{ const handleEdit = (row)=>{
emit('edit',row) emit('edit',row)
} }
const handleRefresh = ()=>{ const handleRefresh = ()=>{
sessionStorage.setItem('search-name',name.value);
emit('refresh',name.value) emit('refresh',name.value)
} }
const handleUpdate = ()=>{ const handleUpdate = (row)=>{
ElMessageBox.confirm('将进入后台自动更新,更新完成后自动重启', '是否下载更新?', { const updateInfo =updater.value.list[row.MachineId];
confirmButtonText: '确定', if(!updateInfo){
cancelButtonText: '取消', ElMessage.error('未检测到更新');
type: 'warning' return;
}).then(() => { }
}).catch(() => {}); //未检测,检测中,下载中,解压中
if([0,1,3,5].indexOf(updateInfo.Status)>=0){
ElMessage.error('操作中,请稍后!');
return;
}
//已检测
if(updateInfo.Status == 2){
ElMessageBox.confirm('确定要更新吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
confirm(row.MachineId);
}).catch(() => {});
}
//已解压
else if(updateInfo.Status == 6){
ElMessageBox.confirm('确定关闭程序吗?如果你是以服务形式安装,关闭后应该会自动再次启动', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
exit(row.MachineId);
}).catch(() => {});
}
} }
return { return {
updater, handleEdit,handleRefresh,version,name,handleUpdate handleEdit,handleRefresh,version,name,updater,updateText,updateColor,handleUpdate
} }
} }
} }
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
@keyframes loading {
from{transform:rotate(0deg)}
to{transform:rotate(360deg)}
}
a{ a{
color:#666; color:#666;
text-decoration: underline; text-decoration: underline;
&.green{color:green;font-weight:bold;}
} }
a.green{color:green}
a.download{ a.download{
margin-left:.6rem margin-left:.6rem
.el-icon{vertical-align:middle;color:red;font-weight:bold;} .el-icon{
vertical-align:middle;font-weight:bold;
&.green{color:green}
&.red{color:red}
&.loading{
animation:loading 1s linear infinite;
}
}
span{
&.green{color:green}
&.red{color:red}
&.yellow{color:#e68906}
}
} }
.el-input{ .el-input{

View File

@@ -2,7 +2,7 @@
<el-table-column prop="forward" label="端口转发"> <el-table-column prop="forward" label="端口转发">
<template #default="scope"> <template #default="scope">
<template v-if="scope.row.showForward"> <template v-if="!scope.row.isSelf">
<div> <div>
<ul class="list forward"> <ul class="list forward">
<template v-if="forward.list[scope.row.MachineId] && forward.list[scope.row.MachineId].length > 0"> <template v-if="forward.list[scope.row.MachineId] && forward.list[scope.row.MachineId].length > 0">
@@ -25,7 +25,7 @@
</ul> </ul>
</div> </div>
</template> </template>
<template v-else-if="scope.row.showSForward"> <template v-else-if="!scope.row.isSelf">
<div> <div>
<ul class="list sforward"> <ul class="list sforward">
<template v-if="sforward.list && sforward.list.length > 0"> <template v-if="sforward.list && sforward.list.length > 0">

View File

@@ -55,6 +55,7 @@ import { provideForward } from './forward'
import { provideConnections } from './connections' import { provideConnections } from './connections'
import { provideSforward } from './sforward' import { provideSforward } from './sforward'
import { provideDevices } from './devices' import { provideDevices } from './devices'
import { provideUpdater } from './updater'
export default { export default {
components: {Device,DeviceEdit,Tunnel,TunnelEdit,ConnectionsEdit, Tuntap,TuntapEdit, Forward,ForwardEdit,ForwardCopy,SForwardEdit,SForwardCopy }, components: {Device,DeviceEdit,Tunnel,TunnelEdit,ConnectionsEdit, Tuntap,TuntapEdit, Forward,ForwardEdit,ForwardCopy,SForwardEdit,SForwardCopy },
setup(props) { setup(props) {
@@ -77,6 +78,7 @@ export default {
handleTunnelConnections,clearConnectionsTimeout handleTunnelConnections,clearConnectionsTimeout
} = provideConnections(); } = provideConnections();
const {_getUpdater,clearUpdaterTimeout} = provideUpdater();
const _handleForwardEdit = (machineId) => { const _handleForwardEdit = (machineId) => {
handleForwardEdit(machineId,devices.page.List.filter(c => c.MachineId == machineId)[0].MachineName); handleForwardEdit(machineId,devices.page.List.filter(c => c.MachineId == machineId)[0].MachineName);
@@ -85,8 +87,11 @@ export default {
const handlePageRefresh = (name)=>{ const handlePageRefresh = (name)=>{
devices.page.Request.Name = name || ''; devices.page.Request.Name = name || '';
if(devices.page.Request.Name){ if(devices.page.Request.Name){
//从虚拟网卡里查找
devices.page.Request.Ids = getTuntapMachines(devices.page.Request.Name) devices.page.Request.Ids = getTuntapMachines(devices.page.Request.Name)
//从端口转发里查找
.concat(getForwardMachines(devices.page.Request.Name)) .concat(getForwardMachines(devices.page.Request.Name))
//从服务器代理穿透里查找
.concat(getSForwardMachines(devices.page.Request.Name)) .concat(getSForwardMachines(devices.page.Request.Name))
.reduce((arr,id)=>{ .reduce((arr,id)=>{
if(arr.indexOf(id) == -1){ if(arr.indexOf(id) == -1){
@@ -125,6 +130,8 @@ export default {
_getForwardInfo(); _getForwardInfo();
_getSForwardInfo(); _getSForwardInfo();
_getUpdater();
_testTargetForwardInfo(); _testTargetForwardInfo();
_testListenForwardInfo(); _testListenForwardInfo();
_testLocalSForwardInfo(); _testLocalSForwardInfo();
@@ -136,6 +143,8 @@ export default {
clearTunnelTimeout(); clearTunnelTimeout();
clearForwardTimeout(); clearForwardTimeout();
clearSForwardTimeout(); clearSForwardTimeout();
clearUpdaterTimeout();
}); });
return { return {

View File

@@ -21,7 +21,7 @@ export const provideConnections = () => {
}); });
provide(forwardConnectionsSymbol, forwardConnections); provide(forwardConnectionsSymbol, forwardConnections);
const _getForwardConnections = () => { const _getForwardConnections = () => {
if (globalData.value.connected) { if (globalData.value.api.connected) {
getForwardConnections().then((res) => { getForwardConnections().then((res) => {
parseConnections(res, removeForwardConnection); parseConnections(res, removeForwardConnection);
forwardConnections.value.list = res; forwardConnections.value.list = res;
@@ -39,7 +39,7 @@ export const provideConnections = () => {
}); });
provide(tuntapConnectionsSymbol, tuntapConnections); provide(tuntapConnectionsSymbol, tuntapConnections);
const _getTuntapConnections = () => { const _getTuntapConnections = () => {
if (globalData.value.connected) { if (globalData.value.api.connected) {
getTuntapConnections().then((res) => { getTuntapConnections().then((res) => {
parseConnections(res, removeTuntapConnection); parseConnections(res, removeTuntapConnection);
tuntapConnections.value.list = res; tuntapConnections.value.list = res;

View File

@@ -24,9 +24,6 @@ export const provideDevices = () => {
devices.page.Request = res.Request; devices.page.Request = res.Request;
devices.page.Count = res.Count; devices.page.Count = res.Count;
for (let j in res.List) { for (let j in res.List) {
res.List[j].showTunnel = machineId.value != res.List[j].MachineId;
res.List[j].showForward = machineId.value != res.List[j].MachineId;
res.List[j].showSForward = machineId.value == res.List[j].MachineId;
res.List[j].showDel = machineId.value != res.List[j].MachineId && res.List[j].Connected == false; res.List[j].showDel = machineId.value != res.List[j].MachineId && res.List[j].Connected == false;
res.List[j].isSelf = machineId.value == res.List[j].MachineId; res.List[j].isSelf = machineId.value == res.List[j].MachineId;
} }
@@ -34,7 +31,7 @@ export const provideDevices = () => {
}).catch((err) => { }); }).catch((err) => { });
} }
const _getSignList1 = () => { const _getSignList1 = () => {
if (globalData.value.connected) { if (globalData.value.api.connected) {
getSignInList(devices.page.Request).then((res) => { getSignInList(devices.page.Request).then((res) => {
for (let j in res.List) { for (let j in res.List) {
const item = devices.page.List.filter(c => c.MachineId == res.List[j].MachineId)[0]; const item = devices.page.List.filter(c => c.MachineId == res.List[j].MachineId)[0];
@@ -43,9 +40,6 @@ export const provideDevices = () => {
item.Version = res.List[j].Version; item.Version = res.List[j].Version;
item.LastSignIn = res.List[j].LastSignIn; item.LastSignIn = res.List[j].LastSignIn;
item.Args = res.List[j].Args; item.Args = res.List[j].Args;
item.showTunnel = machineId.value != res.List[j].MachineId;
item.showForward = machineId.value != res.List[j].MachineId;
item.showSForward = machineId.value == res.List[j].MachineId;
item.showDel = machineId.value != res.List[j].MachineId && res.List[j].Connected == false; item.showDel = machineId.value != res.List[j].MachineId && res.List[j].Connected == false;
item.isSelf = machineId.value == res.List[j].MachineId; item.isSelf = machineId.value == res.List[j].MachineId;
} }

View File

@@ -16,15 +16,15 @@ export const provideForward = () => {
}); });
provide(forwardSymbol, forward); provide(forwardSymbol, forward);
const _getForwardInfo = () => { const _getForwardInfo = () => {
if (globalData.value.connected) { if (globalData.value.api.connected) {
getForwardInfo().then((res) => { getForwardInfo().then((res) => {
forward.value.list = res; forward.value.list = res;
forward.value.timer = setTimeout(_getForwardInfo, 1000); forward.value.timer = setTimeout(_getForwardInfo, 1020);
}).catch(() => { }).catch(() => {
forward.value.timer = setTimeout(_getForwardInfo, 1000); forward.value.timer = setTimeout(_getForwardInfo, 1020);
}); });
} else { } else {
forward.value.timer = setTimeout(_getForwardInfo, 1000); forward.value.timer = setTimeout(_getForwardInfo, 1020);
} }
} }
const handleForwardEdit = (machineId, machineName) => { const handleForwardEdit = (machineId, machineName) => {

View File

@@ -16,15 +16,15 @@ export const provideSforward = () => {
}); });
provide(sforwardSymbol, sforward); provide(sforwardSymbol, sforward);
const _getSForwardInfo = () => { const _getSForwardInfo = () => {
if (globalData.value.connected) { if (globalData.value.api.connected) {
getSForwardInfo().then((res) => { getSForwardInfo().then((res) => {
sforward.value.list = res; sforward.value.list = res;
sforward.value.timer = setTimeout(_getSForwardInfo, 1000); sforward.value.timer = setTimeout(_getSForwardInfo, 1040);
}).catch(() => { }).catch(() => {
sforward.value.timer = setTimeout(_getSForwardInfo, 1000); sforward.value.timer = setTimeout(_getSForwardInfo, 1040);
}); });
} else { } else {
sforward.value.timer = setTimeout(_getSForwardInfo, 1000); sforward.value.timer = setTimeout(_getSForwardInfo, 1040);
} }
} }
const handleSForwardEdit = () => { const handleSForwardEdit = () => {

View File

@@ -15,18 +15,18 @@ export const provideTunnel = () => {
}); });
provide(tunnelSymbol, tunnel); provide(tunnelSymbol, tunnel);
const _getTunnelInfo = () => { const _getTunnelInfo = () => {
if (globalData.value.connected) { if (globalData.value.api.connected) {
getTunnelInfo(tunnel.value.hashcode.toString()).then((res) => { getTunnelInfo(tunnel.value.hashcode.toString()).then((res) => {
tunnel.value.hashcode = res.HashCode; tunnel.value.hashcode = res.HashCode;
if (res.List) { if (res.List) {
tunnel.value.list = res.List; tunnel.value.list = res.List;
} }
tunnel.value.timer = setTimeout(_getTunnelInfo, 1000); tunnel.value.timer = setTimeout(_getTunnelInfo, 1060);
}).catch(() => { }).catch(() => {
tunnel.value.timer = setTimeout(_getTunnelInfo, 1000); tunnel.value.timer = setTimeout(_getTunnelInfo, 1060);
}); });
} else { } else {
tunnel.value.timer = setTimeout(_getTunnelInfo, 1000); tunnel.value.timer = setTimeout(_getTunnelInfo, 1060);
} }
} }
const handleTunnelEdit = (_tunnel) => { const handleTunnelEdit = (_tunnel) => {

View File

@@ -16,7 +16,7 @@ export const provideTuntap = () => {
provide(tuntapSymbol, tuntap); provide(tuntapSymbol, tuntap);
const _getTuntapInfo = () => { const _getTuntapInfo = () => {
clearTimeout(tuntap.value.timer); clearTimeout(tuntap.value.timer);
if (globalData.value.connected) { if (globalData.value.api.connected) {
getTuntapInfo(tuntap.value.hashcode.toString()).then((res) => { getTuntapInfo(tuntap.value.hashcode.toString()).then((res) => {
tuntap.value.hashcode = res.HashCode; tuntap.value.hashcode = res.HashCode;
if (res.List) { if (res.List) {

View File

@@ -0,0 +1,37 @@
import { getUpdater } from "@/apis/updater";
import { injectGlobalData } from "@/provide";
import { inject, provide, ref } from "vue";
const updaterSymbol = Symbol();
export const provideUpdater = () => {
const globalData = injectGlobalData();
const updater = ref({
timer: 0,
list: {}
});
provide(updaterSymbol, updater);
const _getUpdater = () => {
if (globalData.value.api.connected) {
getUpdater().then((res) => {
updater.value.list = res;
updater.value.timer = setTimeout(_getUpdater, 800);
}).catch(() => {
updater.value.timer = setTimeout(_getUpdater, 800);
});
} else {
updater.value.timer = setTimeout(_getUpdater, 800);
}
}
const clearUpdaterTimeout = () => {
clearTimeout(updater.value.timer);
}
return {
updater, _getUpdater, clearUpdaterTimeout
}
}
export const useUpdater = () => {
return inject(updaterSymbol);
}

View File

@@ -21,7 +21,7 @@ export default {
const globalData = injectGlobalData(); const globalData = injectGlobalData();
const state = reactive({ const state = reactive({
tab:settingComponents[0].name, tab:settingComponents[0].name,
connected:computed(()=>globalData.value.connected && globalData.value.configed), connected:computed(()=>globalData.value.api.connected && globalData.value.config.configed),
}); });
return { return {
state,settingComponents state,settingComponents

View File

@@ -17,7 +17,7 @@
<EnablePreviewFeatures>true</EnablePreviewFeatures> <EnablePreviewFeatures>true</EnablePreviewFeatures>
<ServerGarbageCollection>false</ServerGarbageCollection> <ServerGarbageCollection>false</ServerGarbageCollection>
<Title>linker</Title> <Title>linker</Title>
<Version>1.1.1</Version> <Version>1.1.2</Version>
<Authors>snltty</Authors> <Authors>snltty</Authors>
<Company>snltty</Company> <Company>snltty</Company>
<Description>linker</Description> <Description>linker</Description>
@@ -25,8 +25,8 @@
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl> <PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl> <RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>
<PackageReleaseNotes>linker</PackageReleaseNotes> <PackageReleaseNotes>linker</PackageReleaseNotes>
<AssemblyVersion>1.1.1.3</AssemblyVersion> <AssemblyVersion>1.1.2.1</AssemblyVersion>
<FileVersion>1.1.1.3</FileVersion> <FileVersion>1.1.2.1</FileVersion>
</PropertyGroup> </PropertyGroup>

View File

@@ -3,6 +3,7 @@ using linker.libs.extends;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
using System.Net.NetworkInformation;
namespace linker.plugins.tuntap.vea namespace linker.plugins.tuntap.vea
{ {
@@ -173,24 +174,14 @@ namespace linker.plugins.tuntap.vea
{ {
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
string output = CommandHelper.Windows(string.Empty, new string[] { "route print" }); NetworkInterface adapter = NetworkInterface.GetAllNetworkInterfaces()
if (output.Contains("IPv4") == false) .FirstOrDefault(c => c.Name == InterfaceName);
if (adapter != null)
{ {
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) interfaceNumber = adapter.GetIPProperties().GetIPv4Properties().Index;
{ return true;
Error = $"route command not found";
LoggerHelper.Instance.Error(Error);
}
return false;
}
foreach (var item in output.Split(Environment.NewLine))
{
if (item.Contains("WireGuard Tunnel"))
{
interfaceNumber = int.Parse(item.Substring(0, item.IndexOf('.')).Trim());
return true;
}
} }
await Task.Delay(1000).ConfigureAwait(false); await Task.Delay(1000).ConfigureAwait(false);
} }
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)

View File

@@ -5,6 +5,7 @@ using linker.client.capi;
using linker.config; using linker.config;
using linker.plugins.updater.messenger; using linker.plugins.updater.messenger;
using MemoryPack; using MemoryPack;
using System.Collections.Concurrent;
namespace linker.plugins.updater namespace linker.plugins.updater
{ {
@@ -23,22 +24,38 @@ namespace linker.plugins.updater
this.config = config; this.config = config;
} }
public UpdateInfo Get(ApiControllerParamsInfo param) public ConcurrentDictionary<string, UpdateInfo> Get(ApiControllerParamsInfo param)
{ {
return updaterTransfer.Get(); return updaterTransfer.Get();
} }
public async Task Update(ApiControllerParamsInfo param) public async Task Confirm(ApiControllerParamsInfo param)
{ {
if (string.IsNullOrWhiteSpace(param.Content) || param.Content == config.Data.Client.Id) if (string.IsNullOrWhiteSpace(param.Content) || param.Content == config.Data.Client.Id)
{ {
updaterTransfer.Update(); updaterTransfer.Confirm();
} }
else else
{ {
await messengerSender.SendOnly(new MessageRequestWrap await messengerSender.SendOnly(new MessageRequestWrap
{ {
Connection = clientSignInState.Connection, Connection = clientSignInState.Connection,
MessengerId = (ushort)UpdaterMessengerIds.UpdateForward, MessengerId = (ushort)UpdaterMessengerIds.ConfirmForward,
Payload = MemoryPackSerializer.Serialize(param.Content)
});
}
}
public async Task Exit(ApiControllerParamsInfo param)
{
if (string.IsNullOrWhiteSpace(param.Content) || param.Content == config.Data.Client.Id)
{
updaterTransfer.Exit();
}
else
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = clientSignInState.Connection,
MessengerId = (ushort)UpdaterMessengerIds.ExitForward,
Payload = MemoryPackSerializer.Serialize(param.Content) Payload = MemoryPackSerializer.Serialize(param.Content)
}); });
} }

View File

@@ -1,73 +1,332 @@
using linker.config; using linker.client;
using linker.config;
using linker.libs; using linker.libs;
using System.Diagnostics; using linker.plugins.updater.messenger;
using linker.server;
using MemoryPack;
using System.Collections.Concurrent;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
namespace linker.plugins.updater namespace linker.plugins.updater
{ {
public sealed class UpdaterTransfer public sealed class UpdaterTransfer
{ {
private UpdateInfo updateInfo; private UpdateInfo updateInfo = new UpdateInfo();
private string rootPath = "./updater"; private ConcurrentDictionary<string, UpdateInfo> updateInfos = new ConcurrentDictionary<string, UpdateInfo>();
private readonly FileConfig fileConfig; private readonly FileConfig fileConfig;
public UpdaterTransfer(FileConfig fileConfig) private readonly MessengerSender messengerSender;
private readonly ClientSignInState clientSignInState;
public UpdaterTransfer(FileConfig fileConfig, MessengerSender messengerSender, ClientSignInState clientSignInState)
{ {
this.fileConfig = fileConfig; this.fileConfig = fileConfig;
RestartUpdater(); this.messengerSender = messengerSender;
LoadUpdater(); this.clientSignInState = clientSignInState;
clientSignInState.NetworkFirstEnabledHandle += () =>
{
LoadTask();
UpdateTask();
};
ClearTempFile();
} }
private void RestartUpdater() /// <summary>
/// 所有客户端的更新信息
/// </summary>
/// <returns></returns>
public ConcurrentDictionary<string, UpdateInfo> Get()
{ {
try return updateInfos;
{
if (OperatingSystem.IsWindows())
File.Copy("linker.updater.exe", "linker.updater.temp.exe", true);
else
File.Copy("linker.updater", "linker.updater.temp", true);
}
catch (Exception)
{
}
foreach (var item in Process.GetProcessesByName("linker.updater.temp"))
{
item.Kill();
}
if (OperatingSystem.IsWindows())
CommandHelper.Execute("linker.updater.temp.exe", rootPath);
else
CommandHelper.Execute("linker.updater.temp", rootPath);
} }
private void LoadUpdater() /// <summary>
/// 确认更新
/// </summary>
public void Confirm()
{ {
try Task.Run(async () =>
{ {
updateInfo = new UpdateInfo await DownloadUpdate();
await ExtractUpdate();
});
}
/// <summary>
/// 关闭程序
/// </summary>
public void Exit()
{
Environment.Exit(1);
}
/// <summary>
/// 来自别的客户端的更新信息
/// </summary>
/// <param name="info"></param>
public void Update(UpdateInfo info)
{
if (string.IsNullOrWhiteSpace(info.MachineId) == false)
{
updateInfos.AddOrUpdate(info.MachineId, info, (a, b) => info);
}
}
private void UpdateTask()
{
Task.Run(async () =>
{
while (true)
{ {
Msg = File.ReadAllText(Path.Join(rootPath, "msg.txt")), if (updateInfo.StatusChanged())
Version = File.ReadAllText(Path.Join(rootPath, "version.txt")) {
}; updateInfo.MachineId = fileConfig.Data.Client.Id;
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = clientSignInState.Connection,
MessengerId = (ushort)UpdaterMessengerIds.UpdateForward,
Payload = MemoryPackSerializer.Serialize(updateInfo),
});
Update(updateInfo);
}
await Task.Delay(1000);
}
});
}
private void LoadTask()
{
Task.Run(async () =>
{
while (true)
{
await GetUpdateInfo();
await Task.Delay(60000);
}
});
}
private async Task GetUpdateInfo()
{
//正在检查,或者已经确认更新了
if (updateInfo.Status == UpdateStatus.Checking || updateInfo.Status > UpdateStatus.Checked)
{
return;
}
UpdateStatus status = updateInfo.Status;
try
{
updateInfo.Status = UpdateStatus.Checking;
using HttpClient httpClient = new HttpClient();
string str = await httpClient.GetStringAsync("http://gh.snltty.com:1808/https://github.com/snltty/linker/releases/latest").WaitAsync(TimeSpan.FromSeconds(15));
Match match = new Regex(@"/snltty/linker/tree/(v[\d.]+)").Match(str);
string tag = match.Groups[1].Value;
string[] msg = new Regex(@"<li>(.+)</li>").Matches(str).Select(c => c.Groups[1].Value).ToArray();
str = await httpClient.GetStringAsync($"http://gh.snltty.com:1808/https://github.com/snltty/linker/releases/expanded_assets/{tag}").WaitAsync(TimeSpan.FromSeconds(15));
string[] urls = new Regex(@"/snltty/linker/releases/(.+)\.zip").Matches(str)
.Select(c => $"http://gh.snltty.com:1808/https://github.com{c.Groups[0].Value}").ToArray();
string system = OperatingSystem.IsWindows() ? "win" : OperatingSystem.IsLinux() ? "linux" : "osx";
string arch = RuntimeInformation.ProcessArchitecture.ToString().ToLower();
updateInfo.Msg = msg;
updateInfo.Url = urls.FirstOrDefault(c => c.Contains($"linker-{system}-{arch}.zip"));
updateInfo.Version = tag;
updateInfo.Status = UpdateStatus.Checked;
} }
catch (Exception) catch (Exception)
{ {
updateInfo.Status = status;
}
}
private async Task ExtractUpdate()
{
//没下载完成
if (updateInfo.Status != UpdateStatus.Downloaded)
{
return;
}
UpdateStatus status = updateInfo.Status;
try
{
updateInfo.Status = UpdateStatus.Extracting;
updateInfo.Current = 0;
updateInfo.Length = 0;
using ZipArchive archive = ZipFile.OpenRead("updater.zip");
updateInfo.Length = archive.Entries.Sum(c => c.Length);
string configPath = Path.GetFullPath("./configs");
foreach (ZipArchiveEntry entry in archive.Entries)
{
string entryPath = Path.GetFullPath(Path.Join("./", entry.FullName.Substring(entry.FullName.IndexOf('/'))));
if (entryPath.EndsWith('\\') || entryPath.EndsWith('/'))
{
continue;
}
if (entryPath.StartsWith(configPath))
{
continue;
}
if (Directory.Exists(Path.GetDirectoryName(entryPath)) == false)
{
Directory.CreateDirectory(Path.GetDirectoryName(entryPath));
}
if (File.Exists(entryPath))
{
try
{
File.Move(entryPath, $"{entryPath}.temp", true);
}
catch (Exception)
{
}
}
using Stream entryStream = entry.Open();
using FileStream fileStream = File.Create(entryPath);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = await entryStream.ReadAsync(buffer)) != 0)
{
await fileStream.WriteAsync(buffer.AsMemory(0, bytesRead));
updateInfo.Current += bytesRead;
}
entryStream.Dispose();
fileStream.Flush();
fileStream.Dispose();
}
archive.Dispose();
File.Delete("updater.zip");
updateInfo.Status = UpdateStatus.Extracted;
}
catch (Exception ex)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{
LoggerHelper.Instance.Error(ex);
}
updateInfo.Status = status;
}
}
private async Task DownloadUpdate()
{
if (updateInfo.Status != UpdateStatus.Checked)
{
return;
}
UpdateStatus status = updateInfo.Status;
try
{
updateInfo.Status = UpdateStatus.Downloading;
updateInfo.Current = 0;
updateInfo.Length = 0;
using HttpClient httpClient = new HttpClient();
using HttpResponseMessage response = await httpClient.GetAsync(updateInfo.Url, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
updateInfo.Length = response.Content.Headers.ContentLength ?? 0;
using Stream contentStream = await response.Content.ReadAsStreamAsync();
using FileStream fileStream = new FileStream("updater.zip", FileMode.OpenOrCreate, FileAccess.ReadWrite);
byte[] buffer = new byte[4096];
int readBytes = 0;
while ((readBytes = await contentStream.ReadAsync(buffer)) != 0)
{
await fileStream.WriteAsync(buffer.AsMemory(0, readBytes));
updateInfo.Current += readBytes;
}
updateInfo.Status = UpdateStatus.Downloaded;
}
catch (Exception ex)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{
LoggerHelper.Instance.Error(ex);
}
try
{
File.Delete("updater.zip");
}
catch (Exception)
{
}
updateInfo.Status = status;
} }
} }
public UpdateInfo Get() private void ClearTempFile(string path = "./")
{ {
LoadUpdater(); string fullPath = Path.GetFullPath(path);
return updateInfo;
foreach (var item in Directory.GetFiles(fullPath).Where(c=>c.EndsWith(".temp")))
{
try
{
File.Delete(item);
}
catch (Exception)
{
}
}
foreach (var item in Directory.GetDirectories(fullPath))
{
ClearTempFile(item);
}
} }
public void Update()
}
[MemoryPackable]
public sealed partial class UpdateInfo
{
[MemoryPackIgnore]
public string Version { get; set; }
[MemoryPackIgnore]
public string[] Msg { get; set; }
[MemoryPackIgnore]
public string Url { get; set; }
public string MachineId { get; set; }
public UpdateStatus Status { get; set; } = UpdateStatus.None;
public long Length { get; set; }
public long Current { get; set; }
private int statusCode = 0;
public bool StatusChanged()
{ {
File.WriteAllText(Path.Join(rootPath, "extract.txt"), $"{fileConfig.Data.Client.Updater.RunCommand}{Environment.NewLine}{fileConfig.Data.Client.Updater.StopCommand}"); int code = (byte)Status ^ Length.GetHashCode() ^ Current.GetHashCode();
bool res = statusCode != code;
statusCode = code;
return res;
} }
} }
public sealed class UpdateInfo public enum UpdateStatus : byte
{ {
public string Version { get; set; } None = 0,
public string Msg { get; set; } Checking = 1,
Checked = 2,
Downloading = 3,
Downloaded = 4,
Extracting = 5,
Extracted = 6
} }
} }

View File

@@ -4,41 +4,7 @@ namespace linker.plugins.updater.config
{ {
public sealed class UpdaterConfigInfo public sealed class UpdaterConfigInfo
{ {
private string runWindows = "sc start linker.service";
private string stopWindows = "sc stop linker.service & taskkill /F /IM linker.exe & taskkill /F /IM linker.tray.win.exe";
private string runLinux = "systemctl start linker";
private string stopLinux = "systemctl stop linker";
private string runOsx = "launchctl start linker";
private string stopOsx = "launchctl stop linker";
private string runCommand = string.Empty;
public string RunCommand
{
get => runCommand; set
{
runCommand = value;
if (string.IsNullOrWhiteSpace(runCommand))
{
runCommand = OperatingSystem.IsWindows() ? runWindows : OperatingSystem.IsLinux() ? runLinux : runOsx;
}
}
}
private string stopCommand = string.Empty;
public string StopCommand
{
get => stopCommand; set
{
stopCommand = value;
if (string.IsNullOrWhiteSpace(stopCommand))
{
stopCommand = OperatingSystem.IsWindows() ? stopWindows : OperatingSystem.IsLinux() ? stopLinux : stopOsx;
}
}
}
} }
} }

View File

@@ -1,5 +1,6 @@
using linker.plugins.signin.messenger; using linker.plugins.signin.messenger;
using linker.server; using linker.server;
using MemoryPack;
namespace linker.plugins.updater.messenger namespace linker.plugins.updater.messenger
{ {
@@ -12,13 +13,33 @@ namespace linker.plugins.updater.messenger
} }
/// <summary> /// <summary>
/// 更新 /// 确认更新
/// </summary>
/// <param name="connection"></param>
[MessengerId((ushort)UpdaterMessengerIds.Confirm)]
public void Confirm(IConnection connection)
{
updaterTransfer.Confirm();
}
/// <summary>
/// 更新信息
/// </summary> /// </summary>
/// <param name="connection"></param> /// <param name="connection"></param>
[MessengerId((ushort)UpdaterMessengerIds.Update)] [MessengerId((ushort)UpdaterMessengerIds.Update)]
public void Update(IConnection connection) public void Update(IConnection connection)
{ {
updaterTransfer.Update(); UpdateInfo info = MemoryPackSerializer.Deserialize<UpdateInfo>(connection.ReceiveRequestWrap.Payload.Span);
updaterTransfer.Update(info);
}
/// <summary>
/// 关闭信息
/// </summary>
/// <param name="connection"></param>
[MessengerId((ushort)UpdaterMessengerIds.Exit)]
public void Exit(IConnection connection)
{
updaterTransfer.Exit();
} }
} }
@@ -35,24 +56,62 @@ namespace linker.plugins.updater.messenger
} }
/// <summary> /// <summary>
/// 广播更新消息 /// 转发确认更新消息
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
[MessengerId((ushort)UpdaterMessengerIds.ConfirmForward)]
public async Task ConfirmForward(IConnection connection)
{
string machineId = MemoryPackSerializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) && signCaching.TryGet(machineId, out SignCacheInfo cache1) && cache.GroupId == cache1.GroupId)
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache1.Connection,
MessengerId = (ushort)UpdaterMessengerIds.Confirm
});
}
}
/// <summary>
/// 转发更新消息
/// </summary> /// </summary>
/// <param name="connection"></param> /// <param name="connection"></param>
[MessengerId((ushort)UpdaterMessengerIds.UpdateForward)] [MessengerId((ushort)UpdaterMessengerIds.UpdateForward)]
public void UpdateForward(IConnection connection) public void UpdateForward(IConnection connection)
{ {
UpdateInfo info = MemoryPackSerializer.Deserialize<UpdateInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache)) if (signCaching.TryGet(connection.Id, out SignCacheInfo cache))
{ {
List<SignCacheInfo> caches = signCaching.Get(cache.GroupId); foreach (var item in signCaching.Get(cache.GroupId).Where(c => c.Connected && c.MachineId != connection.Id))
foreach (SignCacheInfo item in caches.Where(c => c.MachineId != connection.Id && c.Connected))
{ {
_ = messengerSender.SendOnly(new MessageRequestWrap _ = messengerSender.SendOnly(new MessageRequestWrap
{ {
Connection = item.Connection, Connection = item.Connection,
MessengerId = (ushort)UpdaterMessengerIds.Update, MessengerId = (ushort)UpdaterMessengerIds.Update,
Timeout = 1000, Payload = connection.ReceiveRequestWrap.Payload
}); });
} }
}
}
/// <summary>
/// 转发关闭消息
/// </summary>
/// <param name="connection"></param>
[MessengerId((ushort)UpdaterMessengerIds.ExitForward)]
public async Task ExitForward(IConnection connection)
{
string machineId = MemoryPackSerializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache) && signCaching.TryGet(machineId, out SignCacheInfo cache1) && cache.GroupId == cache1.GroupId)
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = cache1.Connection,
MessengerId = (ushort)UpdaterMessengerIds.Exit
});
} }
} }
} }

View File

@@ -7,6 +7,12 @@
UpdateForward = 2601, UpdateForward = 2601,
Update = 2602, Update = 2602,
ConfirmForward = 2603,
Confirm = 2604,
ExitForward = 2605,
Exit = 2606,
Max = 2299 Max = 2299
} }
} }

View File

@@ -27,8 +27,6 @@ for %%r in (win-x64,win-arm64) do (
for %%r in (win-x64,win-arm64,linux-x64,linux-arm64,osx-x64,osx-arm64) do ( for %%r in (win-x64,win-arm64,linux-x64,linux-arm64,osx-x64,osx-arm64) do (
rem dotnet publish ./linker.updater -c release -f net8.0 -o public/publish/%%r/linker-%%r/ -r %%r -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true -p:TrimMode=partial -p:TieredPGO=true -p:DebugType=none -p:DebugSymbols=false -p:EnableCompressionInSingleFile=true -p:DebuggerSupport=false -p:EnableUnsafeBinaryFormatterSerialization=false -p:EnableUnsafeUTF7Encoding=false -p:HttpActivityPropagationSupport=false -p:InvariantGlobalization=true -p:MetadataUpdaterSupport=false -p:UseSystemResourceKeys=true
dotnet publish ./linker -c release -f net8.0 -o ./public/publish/%%r/linker-%%r -r %%r -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true -p:TrimMode=partial -p:TieredPGO=true -p:DebugType=none -p:DebugSymbols=false -p:EnableCompressionInSingleFile=true -p:DebuggerSupport=false -p:EnableUnsafeBinaryFormatterSerialization=false -p:EnableUnsafeUTF7Encoding=false -p:HttpActivityPropagationSupport=false -p:InvariantGlobalization=true -p:MetadataUpdaterSupport=false -p:UseSystemResourceKeys=true dotnet publish ./linker -c release -f net8.0 -o ./public/publish/%%r/linker-%%r -r %%r -p:PublishSingleFile=true -p:PublishTrimmed=true --self-contained true -p:TrimMode=partial -p:TieredPGO=true -p:DebugType=none -p:DebugSymbols=false -p:EnableCompressionInSingleFile=true -p:DebuggerSupport=false -p:EnableUnsafeBinaryFormatterSerialization=false -p:EnableUnsafeUTF7Encoding=false -p:HttpActivityPropagationSupport=false -p:InvariantGlobalization=true -p:MetadataUpdaterSupport=false -p:UseSystemResourceKeys=true
echo F|xcopy "public\\extends\\%%r\\linker-%%r\\*" "public\\publish\\%%r\\linker-%%r\\*" /s /f /h /y echo F|xcopy "public\\extends\\%%r\\linker-%%r\\*" "public\\publish\\%%r\\linker-%%r\\*" /s /f /h /y