mirror of
https://github.com/snltty/linker.git
synced 2025-12-19 01:46:46 +08:00
服务器自动更新
This commit is contained in:
@@ -38,3 +38,10 @@
|
||||
请作者喝一杯咖啡,使其更有精力更新代码
|
||||
<p><img src="./readme/qr.jpg" width="360"></p>
|
||||
</div>
|
||||
|
||||
## 感谢支持
|
||||
|
||||
<a href="https://mi-d.cn" target="_blank">
|
||||
<img src="https://mi-d.cn/wp-content/uploads/2021/12/cropped-1639494965-网站LOGO无字.png" width="40" style="vertical-align: middle;"> 米多贝克</a>
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ sidebar_position: 1
|
||||
|
||||
# 1、首页
|
||||
|
||||
## 1、说明
|
||||
|
||||
1. 使用 .NET8、所有通信功能均支持TCP+UDP
|
||||
2. SSL通信加密,打洞、中继、均支持ssl加密
|
||||
3. 打洞内核**免费开源**,你可以使用**linker.tunnel**库将打洞集成到自己的项目中
|
||||
@@ -12,4 +14,8 @@ sidebar_position: 1
|
||||
6. 打洞失败回退、这需要你中继部署服务器进行中继通信
|
||||
7. 少量的内存占用、易用的web管理页面、高性能的通信
|
||||
|
||||
<a href="https://jq.qq.com/?_wv=1027&k=ucoIVfz4" target="_blank">你可以加入QQ群:1121552990</a>
|
||||
<a href="https://jq.qq.com/?_wv=1027&k=ucoIVfz4" target="_blank">你可以加入QQ群:1121552990 </a>
|
||||
|
||||
## 2、感谢支持
|
||||
|
||||
<a href="https://mi-d.cn" target="_blank"><img src="https://mi-d.cn/wp-content/uploads/2021/12/cropped-1639494965-网站LOGO无字.png" width="40" style={{verticalAlign: 'middle'}} /> 米多贝克</a>
|
||||
@@ -49,5 +49,9 @@ server.json
|
||||
60000
|
||||
]
|
||||
},
|
||||
//服务器更新密钥,客户端配置这个密钥,可以更新服务端
|
||||
"Updater": {
|
||||
"SecretKey": "46760C6B-5EA8-4FCB-B342-1D16A7CE9773"
|
||||
},
|
||||
}
|
||||
```
|
||||
@@ -6,15 +6,16 @@ sidebar_position: 3
|
||||
|
||||
## 1、说明
|
||||
1. **如果你自己部署了服务端,则可以修改服务器相关配置**
|
||||
2. 如果你使用默认的 linker.snltty.com 服务器,你无需修改服务器相关配置,但是你无法使用**中继**和**服务器代理穿透** 功能
|
||||
2. 如果你使用默认的 linker.snltty.com 服务器,你无需修改服务器相关配置,但是你无法使用**中继**、**服务器穿透**、**服务器更新** 功能,这些功能都需要服务端响应的密钥才能操作
|
||||
|
||||
|
||||
## 2、看图
|
||||
1. **信标服务器**,客户端之间交换信息,比如打洞,需要交换外网IP和外网端口
|
||||
2. **外网端口服务器**,用于获取客户端外网IP和外网端口,协助打洞
|
||||
2. **端口服务器**,用于获取客户端外网IP和外网端口,协助打洞
|
||||
3. **打洞协议**,有多种打洞协议,你可以调整顺序,优先使用喜欢的打洞协议进行打洞
|
||||
4. **打洞排除IP**,如果你希望有哪些IP不参与打洞,你可以在这里配置,比如 虚拟网卡 的IP,一些VPN的IP
|
||||
5. **中继加密秘钥**,当客户端与服务端秘钥不一致时,无法使用中继
|
||||
6. **服务器代理穿透**,就是内网穿透,你需要填写服务端的密钥
|
||||
5. **中继服务器**,用于在打洞失败后使用服务器中转进行通信,请填写你服务端的中继密钥,当客户端与服务端秘钥不一致时,无法使用中继
|
||||
6. **服务器穿透**,就是内网穿透,你需要填写服务端的密钥
|
||||
7. **服务器更新**,填写你服务器的更新密钥,让你的客户端有权自动更新服务器
|
||||
|
||||

|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 124 KiB |
@@ -4,9 +4,13 @@ sidebar_position: 3
|
||||
|
||||
# 3.3、虚拟网卡
|
||||
|
||||
|
||||
|
||||
1. 此一项,可以为每个设备安装一个**虚拟网卡**,然后分配一个**IP**,通过IP地址访问设备。不受端口限制。
|
||||
2. 两个设备之间通过**虚拟网卡**互相访问,则两个设备都需要安装虚拟网卡,且IP段一致,比如都是 **192.168.54.x/24**,一个是**192.168.54.2**,一个是**192.168.54.3**,而不能配置为**192.168.54.2**和**192.168.55.3**
|
||||
|
||||
3. 如果你无法开启虚拟网卡,可以到<a target="_blank" href="https://github.com/xjasonlyu/tun2socks/releases/latest">tun2socks</a> 下载对应你系统的版本,覆盖`plugins/tuntap/` 下的 `tun2socks` 或 `tun2socks.exe`
|
||||
|
||||
## 1、配置虚拟网卡IP
|
||||
|
||||
在设备,虚拟网卡一栏,点击IP配置
|
||||
|
||||
@@ -6,8 +6,8 @@ sidebar_position: 1
|
||||
|
||||
## 1、支持msquic
|
||||
|
||||
1. 在windows下,win11支持msquic,但是win10默认并不支持,你可以删除**msquic.dll**,将**msquic-openssl.dll**更名为**msquic.dll**,这或许能让win10支持msquic
|
||||
2. linux 下,请按<a target="_blank" href="https://github.com/dotnet/runtime/tree/main/src/libraries/System.Net.Quic">官方说明</a>安装msquic
|
||||
1. 在windows,如果提示不支持msquic,可以到 <a target="_blank" href="https://github.com/microsoft/msquic/releases/latest">msquic</a> 下载合适你系统的版本,覆盖根目录下的msquic.dll,一般来说,win10或以下版本,需要openssl版本的msquic
|
||||
2. 再linux,请按<a target="_blank" href="https://github.com/dotnet/runtime/tree/main/src/libraries/System.Net.Quic">官方说明</a>安装msquic
|
||||
|
||||
## 2、打洞协议调整
|
||||
|
||||
|
||||
@@ -39,7 +39,15 @@ namespace linker.service
|
||||
{
|
||||
if (Process.GetProcessesByName(mainExeName).Any() == false)
|
||||
{
|
||||
RestartService();
|
||||
if (File.Exists($"{mainExeName}.exe.temp"))
|
||||
{
|
||||
RestartService();
|
||||
}
|
||||
else
|
||||
{
|
||||
KillExe();
|
||||
OpenExe();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
|
||||
@@ -10,6 +10,8 @@ using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Text;
|
||||
using linker.tunnel.wanport;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.IO;
|
||||
|
||||
namespace linker.tunnel.transport
|
||||
{
|
||||
@@ -729,31 +731,23 @@ namespace linker.tunnel.transport
|
||||
}
|
||||
private void TestQuic()
|
||||
{
|
||||
if (QuicListener.IsSupported == false)
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
if (QuicListener.IsSupported == false)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoggerHelper.Instance.Info($"move msquic-openssl.dll -> msquic.dll");
|
||||
File.Move("msquic-openssl.dll", "msquic.dll", true);
|
||||
if (File.Exists("msquic-openssl.dll"))
|
||||
{
|
||||
LoggerHelper.Instance.Info($"copy msquic-openssl.dll -> msquic.dll");
|
||||
File.Move("msquic-openssl.dll", "msquic.dll", true);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
LoggerHelper.Instance.Info($"delete msquic-openssl.dll");
|
||||
File.Delete("msquic-openssl.dll");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,4 +9,25 @@ export const confirm = (machineId, version) => {
|
||||
}
|
||||
export const exit = (machineId) => {
|
||||
return sendWebsocketMsg('updaterclient/exit', machineId);
|
||||
}
|
||||
|
||||
export const getSecretKey = () => {
|
||||
return sendWebsocketMsg('updaterclient/GetSecretKey');
|
||||
}
|
||||
export const setSecretKey = (data) => {
|
||||
return sendWebsocketMsg('updaterclient/SetSecretKey', data);
|
||||
}
|
||||
|
||||
|
||||
export const getUpdaterCurrent = () => {
|
||||
return sendWebsocketMsg('updaterclient/getcurrent');
|
||||
}
|
||||
export const getUpdaterServer = () => {
|
||||
return sendWebsocketMsg('updaterclient/getserver');
|
||||
}
|
||||
export const confirmServer = (version) => {
|
||||
return sendWebsocketMsg('updaterclient/confirmserver', version);
|
||||
}
|
||||
export const exitServer = () => {
|
||||
return sendWebsocketMsg('updaterclient/exitserver');
|
||||
}
|
||||
@@ -46,6 +46,9 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<el-form-item label="更新密钥" prop="updaterSecretKey">
|
||||
<el-input v-model="state.form.updaterSecretKey" maxlength="36" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
@@ -66,6 +69,8 @@ export default {
|
||||
webPort:globalData.value.config.Server.SForward.WebPort,
|
||||
tunnelPort1:globalData.value.config.Server.SForward.TunnelPortRange[0],
|
||||
tunnelPort2:globalData.value.config.Server.SForward.TunnelPortRange[1],
|
||||
|
||||
updaterSecretKey:globalData.value.config.Server.Updater.SecretKey,
|
||||
},
|
||||
rules: {
|
||||
relaySecretKey: [{ required: true, message: "必填", trigger: "blur" }],
|
||||
@@ -142,6 +147,9 @@ export default {
|
||||
SecretKey: state.form.sForwardSecretKey,
|
||||
WebPort: +state.form.webPort,
|
||||
TunnelPortRange: [+state.form.tunnelPort1, +state.form.tunnelPort2]
|
||||
},
|
||||
Updater:{
|
||||
SecretKey: state.form.updaterSecretKey
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
<template>
|
||||
<div class="status-server-wrap" :class="{ connected: state.connected }">
|
||||
<a href="javascript:;" @click="handleConfig">
|
||||
<el-icon size="16"><Promotion /></el-icon>
|
||||
<template v-if="state.connected">信标服务器</template>
|
||||
<template v-else>信标服务器</template>
|
||||
<a href="javascript:;" @click="handleConfig"> <el-icon size="16"><Promotion /></el-icon> 信标服务器</a>
|
||||
<a href="javascript:;" @click="handleUpdate" class="download" :title="updateText()" :class="updateColor()">
|
||||
<span>{{state.version}}</span>
|
||||
<template v-if="updaterCurrent.Version">
|
||||
<template v-if="updaterCurrent.Status == 1">
|
||||
<el-icon size="14" class="loading"><Loading /></el-icon>
|
||||
</template>
|
||||
<template v-else-if="updaterServer.Status == 2">
|
||||
<el-icon size="14"><Download /></el-icon>
|
||||
</template>
|
||||
<template v-else-if="updaterServer.Status == 3 || updaterServer.Status == 5">
|
||||
<el-icon size="14" class="loading"><Loading /></el-icon>
|
||||
<span class="progress" v-if="updaterServer.Length ==0">0%</span>
|
||||
<span class="progress" v-else>{{parseInt(updaterServer.Current/updaterServer.Length*100)}}%</span>
|
||||
</template>
|
||||
<template v-else-if="updaterServer.Status == 6">
|
||||
<el-icon size="14" class="yellow"><CircleCheck /></el-icon>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-icon size="14"><Download /></el-icon>
|
||||
</template>
|
||||
</a>
|
||||
<a href="javascript:;" class="">{{state.version}}</a>
|
||||
</div>
|
||||
<el-dialog v-model="state.show" title="连接设置" width="300">
|
||||
<div>
|
||||
@@ -29,22 +46,26 @@
|
||||
<script>
|
||||
import { setSignIn } from '@/apis/signin';
|
||||
import { injectGlobalData } from '@/provide';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { computed, reactive } from 'vue';
|
||||
import {Promotion} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import {Promotion,Download,Loading,CircleCheck} from '@element-plus/icons-vue'
|
||||
import { confirmServer, exitServer, getUpdaterCurrent, getUpdaterServer } from '@/apis/updater';
|
||||
import { subWebsocketState } from '@/apis/request';
|
||||
export default {
|
||||
components:{Promotion},
|
||||
components:{Promotion,Download,Loading,CircleCheck},
|
||||
setup(props) {
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
const updaterCurrent = ref({Version: '', Status: 0, Length: 0, Current: 0});
|
||||
const updaterServer = ref({Version: '', Status: 0, Length: 0, Current: 0});
|
||||
|
||||
const state = reactive({
|
||||
show: false,
|
||||
loading: false,
|
||||
|
||||
connected: computed(() => globalData.value.signin.Connected),
|
||||
connecting: computed(() => globalData.value.signin.Connecting),
|
||||
version: computed(() => globalData.value.signin.Version),
|
||||
server: computed(() => globalData.value.config.Client.Server),
|
||||
serverLength: computed(() => (globalData.value.config.Running.Client.Servers || []).length),
|
||||
|
||||
form: {
|
||||
name: globalData.value.config.Client.Name,
|
||||
groupid: globalData.value.config.Client.GroupId,
|
||||
@@ -52,6 +73,95 @@ export default {
|
||||
rules: {},
|
||||
});
|
||||
|
||||
const _getUpdaterCurrent = ()=>{
|
||||
getUpdaterCurrent().then((res)=>{
|
||||
updaterCurrent.value.Version = res.Version;
|
||||
updaterCurrent.value.Status = res.Status;
|
||||
updaterCurrent.value.Length = res.Length;
|
||||
updaterCurrent.value.Current = res.Current;
|
||||
setTimeout(()=>{
|
||||
_getUpdaterCurrent();
|
||||
},1000);
|
||||
}).catch(()=>{
|
||||
setTimeout(()=>{
|
||||
_getUpdaterCurrent();
|
||||
},1000);
|
||||
})
|
||||
}
|
||||
const _getUpdaterServer = ()=>{
|
||||
getUpdaterServer().then((res)=>{
|
||||
updaterServer.value.Version = res.Version;
|
||||
updaterServer.value.Status = res.Status;
|
||||
updaterServer.value.Length = res.Length;
|
||||
updaterServer.value.Current = res.Current;
|
||||
if(updaterServer.value.Status > 2 && updaterServer.value.Status < 6){
|
||||
setTimeout(()=>{
|
||||
_getUpdaterServer();
|
||||
},1000);
|
||||
}
|
||||
}).catch(()=>{
|
||||
setTimeout(()=>{
|
||||
_getUpdaterServer();
|
||||
},1000);
|
||||
});
|
||||
}
|
||||
const updateText = ()=>{
|
||||
if(!updaterCurrent.value.Version){
|
||||
return '未检测到更新';
|
||||
}
|
||||
if(updaterServer.value.Status <= 2) {
|
||||
return state.version != updaterCurrent.value.Version
|
||||
? `不是最新版本(${updaterCurrent.value.Version}),建议更新`
|
||||
: '是最新版本,但我无法阻止你喜欢更新'
|
||||
}
|
||||
return {
|
||||
3:'正在下载',
|
||||
4:'已下载',
|
||||
5:'正在解压',
|
||||
6:'已解压,请重启',
|
||||
}[updaterServer.value.Status];
|
||||
}
|
||||
const updateColor = ()=>{
|
||||
return state.version != updaterCurrent.value.Version ? 'yellow' :'green'
|
||||
}
|
||||
const handleUpdate = ()=>{
|
||||
if(!updaterCurrent.value.Version){
|
||||
ElMessage.error('未检测到更新');
|
||||
return;
|
||||
}
|
||||
//未检测,检测中,下载中,解压中
|
||||
if([0,1,3,5].indexOf(updaterServer.value.Status)>=0){
|
||||
ElMessage.error('操作中,请稍后!');
|
||||
return;
|
||||
}
|
||||
//已解压
|
||||
if(updaterServer.value.Status == 6){
|
||||
ElMessageBox.confirm('确定关闭服务端吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
exitServer();
|
||||
}).catch(() => {});
|
||||
return;
|
||||
}
|
||||
|
||||
//已检测
|
||||
if(updaterCurrent.value.Status == 2){
|
||||
ElMessageBox.confirm('确定更新服务端吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
confirmServer(updaterCurrent.value.Version).then(()=>{
|
||||
setTimeout(()=>{
|
||||
_getUpdaterServer();
|
||||
},1000);
|
||||
});
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
const handleConfig = () => {
|
||||
state.form.name = globalData.value.config.Client.Name;
|
||||
state.form.groupid = globalData.value.config.Client.GroupId;
|
||||
@@ -69,19 +179,33 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
subWebsocketState((state)=>{
|
||||
if(state){
|
||||
_getUpdaterCurrent();
|
||||
_getUpdaterServer();
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
state, handleConfig, handleSave,
|
||||
state, handleConfig, handleSave,updaterCurrent,updaterServer,handleUpdate,updateText,updateColor
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
|
||||
@keyframes loading {
|
||||
from{transform:rotate(0deg)}
|
||||
to{transform:rotate(360deg)}
|
||||
}
|
||||
|
||||
.status-server-wrap{
|
||||
padding-right:.5rem;
|
||||
a{color:#333;}
|
||||
a+a{margin-left:.6rem;}
|
||||
span{border-radius:1rem;background-color:rgba(0,0,0,0.1);padding:0 .6rem; margin-left:.2rem}
|
||||
|
||||
&.connected {
|
||||
a{color:green;font-weight:bold;}
|
||||
@@ -90,6 +214,20 @@ export default {
|
||||
.el-icon{
|
||||
vertical-align:text-bottom;
|
||||
}
|
||||
|
||||
a.download{
|
||||
&.green{color:green}
|
||||
&.red{color:red}
|
||||
&.yellow{color:#e68906}
|
||||
.el-icon{
|
||||
font-weight:bold;
|
||||
&.yellow{color:#e68906}
|
||||
&.loading{
|
||||
animation:loading 1s linear infinite;
|
||||
}
|
||||
margin-left:.3rem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -18,8 +18,8 @@
|
||||
<p class="flex">
|
||||
<span>{{ scope.row.IP }}</span>
|
||||
<span class="flex-1"></span>
|
||||
<a href="javascript:;" class="download" title="下载更新" @click="handleUpdate(scope.row)">
|
||||
<span :title="updateText(scope.row)" :class="updateColor(scope.row)">
|
||||
<a href="javascript:;" class="download" title="下载更新" @click="handleUpdate(scope.row)" :title="updateText(scope.row)" :class="updateColor(scope.row)">
|
||||
<span>
|
||||
<span>{{scope.row.Version}}</span>
|
||||
<template v-if="updater.list[scope.row.MachineId]">
|
||||
<template v-if="updater.list[scope.row.MachineId].Status == 1">
|
||||
@@ -34,7 +34,7 @@
|
||||
<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>
|
||||
<el-icon size="14" class="yellow"><CircleCheck /></el-icon>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
@@ -51,7 +51,7 @@
|
||||
import { injectGlobalData } from '@/provide';
|
||||
import { computed, ref,h } from 'vue';
|
||||
import {StarFilled,Search,Download,Loading,CircleCheck} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox,ElSelect,ElOption, arrowMiddleware } from 'element-plus';
|
||||
import { ElMessage, ElMessageBox,ElSelect,ElOption } from 'element-plus';
|
||||
import { confirm, exit } from '@/apis/updater';
|
||||
import { useUpdater } from './updater';
|
||||
|
||||
@@ -64,7 +64,7 @@ export default {
|
||||
const globalData = injectGlobalData();
|
||||
const updater = useUpdater();
|
||||
const serverVersion = computed(()=>globalData.value.signin.Version);
|
||||
const updaterVersion = computed(()=>updater.value.version);
|
||||
const updaterVersion = computed(()=>updater.value.current.Version);
|
||||
|
||||
const updateText = (row)=>{
|
||||
if(!updater.value.list[row.MachineId]){
|
||||
@@ -74,7 +74,7 @@ export default {
|
||||
return row.Version != serverVersion.value
|
||||
? `与服务器版本(${serverVersion.value})不一致,建议更新`
|
||||
: updaterVersion.value != row.Version
|
||||
? `不是最新版本(${updaterVersion.value}),建议更新` : '版本一致,但我无法阻止你喜欢更新'
|
||||
? `不是最新版本(${updaterVersion.value}),建议更新` : '是最新版本,但我无法阻止你喜欢更新'
|
||||
}
|
||||
return {
|
||||
3:'正在下载',
|
||||
@@ -170,22 +170,17 @@ a{
|
||||
|
||||
a.download{
|
||||
margin-left:.6rem
|
||||
&.green{color:green}
|
||||
&.red{color:red}
|
||||
&.yellow{color:#e68906}
|
||||
.el-icon{
|
||||
vertical-align:middle;font-weight:bold;
|
||||
&.green{color:green}
|
||||
&.red{color:red}
|
||||
|
||||
&.yellow{color:#e68906}
|
||||
&.loading{
|
||||
animation:loading 1s linear infinite;
|
||||
}
|
||||
|
||||
margin-left:.6rem
|
||||
}
|
||||
|
||||
span{
|
||||
&.green{color:green}
|
||||
&.red{color:red}
|
||||
&.yellow{color:#e68906}
|
||||
margin-left:.3rem
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export const provideUpdater = () => {
|
||||
const updater = ref({
|
||||
timer: 0,
|
||||
list: {},
|
||||
version: ''
|
||||
current: { Version: '', Status: 0, Length: 0, Current: 0 }
|
||||
});
|
||||
provide(updaterSymbol, updater);
|
||||
const _getUpdater = () => {
|
||||
@@ -16,7 +16,10 @@ export const provideUpdater = () => {
|
||||
getUpdater().then((res) => {
|
||||
const self = Object.values(res).filter(c => !!c.Version)[0];
|
||||
if (self) {
|
||||
updater.value.version = self.Version;
|
||||
updater.value.current.Version = self.Version;
|
||||
updater.value.current.Status = self.Status;
|
||||
updater.value.current.Length = self.Length;
|
||||
updater.value.current.Current = self.Current;
|
||||
}
|
||||
updater.value.list = res;
|
||||
updater.value.timer = setTimeout(_getUpdater, 800);
|
||||
|
||||
@@ -16,8 +16,7 @@ export default {
|
||||
components:{},
|
||||
setup(props) {
|
||||
const files = require.context('./', true, /.+\.vue/);
|
||||
const settingComponents = files.keys().filter(c=>c != './Index.vue').map(c => files(c).default).sort((a,b)=>a.order-b.order);
|
||||
|
||||
const settingComponents = files.keys().filter(c=>c != './Index.vue' && c != './Version.vue').map(c => files(c).default).sort((a,b)=>a.order-b.order);
|
||||
const globalData = injectGlobalData();
|
||||
const state = reactive({
|
||||
tab:settingComponents[0].name,
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ElMessage } from 'element-plus';
|
||||
import { computed, inject, onMounted, reactive } from 'vue'
|
||||
import Version from './Version.vue';
|
||||
export default {
|
||||
label:'服务器代理穿透',
|
||||
label:'服务器穿透',
|
||||
name:'sforward',
|
||||
order:5,
|
||||
components:{Version},
|
||||
|
||||
@@ -81,7 +81,7 @@ import { ElMessage } from 'element-plus';
|
||||
import { computed, inject, onMounted, reactive } from 'vue'
|
||||
import Version from './Version.vue';
|
||||
export default {
|
||||
label:'外网端口服务器',
|
||||
label:'端口服务器',
|
||||
name:'tunnelServers',
|
||||
order:1,
|
||||
components:{Version},
|
||||
|
||||
57
linker.web/src/views/settings/Updater.vue
Normal file
57
linker.web/src/views/settings/Updater.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<Version ckey="updater"/>
|
||||
<div style="width: 30rem;padding: 5rem 0; margin: 0 auto;">
|
||||
<p class="t-c">
|
||||
服务器更新密钥
|
||||
</p>
|
||||
<p>
|
||||
<el-input type="password" show-password v-model="state.secretKey" maxlength="36" @blur="handleChange" />
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { getSecretKey,setSecretKey } from '@/apis/updater';
|
||||
import { injectGlobalData } from '@/provide';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { computed, inject, onMounted, reactive } from 'vue'
|
||||
import Version from './Version.vue';
|
||||
export default {
|
||||
label:'服务器更新',
|
||||
name:'updater',
|
||||
order:6,
|
||||
components:{Version},
|
||||
setup(props) {
|
||||
const globalData = injectGlobalData();
|
||||
const state = reactive({
|
||||
secretKey:''
|
||||
});
|
||||
|
||||
const _getSecretKey = ()=>{
|
||||
getSecretKey().then((res)=>{
|
||||
state.secretKey = res;
|
||||
});
|
||||
}
|
||||
|
||||
const _setSecretKey = ()=>{
|
||||
if(!state.secretKey) return;
|
||||
setSecretKey(state.secretKey).then(()=>{
|
||||
ElMessage.success('已操作');
|
||||
}).catch(()=>{
|
||||
ElMessage.success('操作失败');
|
||||
});
|
||||
}
|
||||
const handleChange = ()=>{
|
||||
_setSecretKey();
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
_getSecretKey();
|
||||
});
|
||||
|
||||
return {state,handleChange}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="stylus" scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="running-version-wrap">
|
||||
<span>配置版本 : {{version}}</span>
|
||||
<span>配置版本 : {{version || 1}}</span>
|
||||
<el-button size="small" @click=handleEdit>手动修改版本</el-button>
|
||||
<span>高版本一端自动同步到低版本一端</span>
|
||||
</div>
|
||||
|
||||
@@ -6,14 +6,16 @@ RUN echo "https://mirrors.tuna.tsinghua.edu.cn/alpine/latest-stable/main/" > /et
|
||||
&& ln -snf /usr/share/zoneinfo/$clTZ /etc/localtime \
|
||||
&& echo $TZ > /etc/timezone
|
||||
|
||||
EXPOSE 1800/tcp
|
||||
EXPOSE 1801/tcp
|
||||
EXPOSE 1802/tcp
|
||||
EXPOSE 1802/udp
|
||||
EXPOSE 1803/tcp
|
||||
EXPOSE 1803/udp
|
||||
EXPOSE 1804/tcp
|
||||
EXPOSE 1804/udp
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
|
||||
|
||||
ENTRYPOINT ["./linker.run"]
|
||||
ENTRYPOINT ["./linker"]
|
||||
@@ -162,7 +162,7 @@ namespace linker.config
|
||||
|
||||
#if DEBUG
|
||||
private LoggerTypes loggerType { get; set; } = LoggerTypes.DEBUG;
|
||||
public bool Install { get; set; } = true;
|
||||
public bool Install { get; set; } = false;
|
||||
#else
|
||||
private LoggerTypes loggerType { get; set; } = LoggerTypes.WARNING;
|
||||
public bool Install { get; set; } = false;
|
||||
|
||||
BIN
linker/msquic-arm64.dll
Normal file
BIN
linker/msquic-arm64.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
linker/msquic-openssl3-arm64.dll
Normal file
BIN
linker/msquic-openssl3-arm64.dll
Normal file
Binary file not shown.
BIN
linker/msquic-openssl3-x64.dll
Normal file
BIN
linker/msquic-openssl3-x64.dll
Normal file
Binary file not shown.
Binary file not shown.
@@ -39,9 +39,12 @@ namespace linker.plugins.config
|
||||
{
|
||||
config.Data.Server.ServicePort = info.Server.ServicePort;
|
||||
config.Data.Server.Relay.SecretKey = info.Server.Relay.SecretKey;
|
||||
|
||||
config.Data.Server.SForward.SecretKey = info.Server.SForward.SecretKey;
|
||||
config.Data.Server.SForward.WebPort = info.Server.SForward.WebPort;
|
||||
config.Data.Server.SForward.TunnelPortRange = info.Server.SForward.TunnelPortRange;
|
||||
|
||||
config.Data.Server.Updater.SecretKey = info.Server.Updater.SecretKey;
|
||||
}
|
||||
|
||||
config.Data.Common.Modes = info.Common.Modes;
|
||||
@@ -70,6 +73,11 @@ namespace linker.plugins.config
|
||||
public int ServicePort { get; set; }
|
||||
public ConfigInstallServerRelayInfo Relay { get; set; }
|
||||
public ConfigInstallServerSForwardInfo SForward { get; set; }
|
||||
public ConfigInstallServerUpdaterInfo Updater { get; set; }
|
||||
}
|
||||
public sealed class ConfigInstallServerUpdaterInfo
|
||||
{
|
||||
public string SecretKey { get; set; }
|
||||
}
|
||||
public sealed class ConfigInstallServerRelayInfo
|
||||
{
|
||||
|
||||
@@ -8,24 +8,80 @@ using MemoryPack;
|
||||
using System.Collections.Concurrent;
|
||||
using linker.plugins.updater.config;
|
||||
using linker.libs.extends;
|
||||
using linker.client.config;
|
||||
|
||||
namespace linker.plugins.updater
|
||||
{
|
||||
public sealed class UpdaterClientApiController : IApiClientController
|
||||
{
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly UpdaterTransfer updaterTransfer;
|
||||
private readonly UpdaterClientTransfer updaterTransfer;
|
||||
private readonly ClientSignInState clientSignInState;
|
||||
private readonly FileConfig config;
|
||||
private readonly UpdaterClientTransfer updaterClientTransfer;
|
||||
private readonly RunningConfig runningConfig;
|
||||
|
||||
public UpdaterClientApiController(MessengerSender messengerSender, UpdaterTransfer updaterTransfer, ClientSignInState clientSignInState, FileConfig config)
|
||||
public UpdaterClientApiController(MessengerSender messengerSender, UpdaterClientTransfer updaterTransfer, ClientSignInState clientSignInState, FileConfig config, UpdaterClientTransfer updaterClientTransfer, RunningConfig runningConfig)
|
||||
{
|
||||
this.messengerSender = messengerSender;
|
||||
this.updaterTransfer = updaterTransfer;
|
||||
this.clientSignInState = clientSignInState;
|
||||
this.config = config;
|
||||
this.updaterClientTransfer = updaterClientTransfer;
|
||||
this.runningConfig = runningConfig;
|
||||
}
|
||||
|
||||
public string GetSecretKey(ApiControllerParamsInfo param)
|
||||
{
|
||||
return updaterClientTransfer.GetSecretKey();
|
||||
}
|
||||
public void SetSecretKey(ApiControllerParamsInfo param)
|
||||
{
|
||||
updaterClientTransfer.SetSecretKey(param.Content);
|
||||
}
|
||||
|
||||
public UpdateInfo GetCurrent(ApiControllerParamsInfo param)
|
||||
{
|
||||
var updaters = updaterTransfer.Get();
|
||||
if(updaters.TryGetValue(config.Data.Client.Id,out UpdateInfo info))
|
||||
{
|
||||
return info;
|
||||
}
|
||||
return new UpdateInfo { };
|
||||
}
|
||||
public async Task<UpdateInfo> GetServer(ApiControllerParamsInfo param)
|
||||
{
|
||||
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
|
||||
{
|
||||
Connection = clientSignInState.Connection,
|
||||
MessengerId = (ushort)UpdaterMessengerIds.UpdateServer,
|
||||
});
|
||||
if (resp.Code == MessageResponeCodes.OK && resp.Data.Length > 0)
|
||||
{
|
||||
return MemoryPackSerializer.Deserialize<UpdateInfo>(resp.Data.Span);
|
||||
}
|
||||
return new UpdateInfo();
|
||||
}
|
||||
public async Task ConfirmServer(ApiControllerParamsInfo param)
|
||||
{
|
||||
await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = clientSignInState.Connection,
|
||||
MessengerId = (ushort)UpdaterMessengerIds.ConfirmServer,
|
||||
Payload = MemoryPackSerializer.Serialize(new UpdaterConfirmServerInfo { SecretKey = runningConfig.Data.UpdaterSecretKey, Version = param.Content })
|
||||
});
|
||||
}
|
||||
public async Task ExitServer(ApiControllerParamsInfo param)
|
||||
{
|
||||
await messengerSender.SendOnly(new MessageRequestWrap
|
||||
{
|
||||
Connection = clientSignInState.Connection,
|
||||
MessengerId = (ushort)UpdaterMessengerIds.ExitServer,
|
||||
Payload = MemoryPackSerializer.Serialize(new UpdaterConfirmServerInfo { SecretKey = runningConfig.Data.UpdaterSecretKey, Version = string.Empty })
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public ConcurrentDictionary<string, UpdateInfo> Get(ApiControllerParamsInfo param)
|
||||
{
|
||||
return updaterTransfer.Get();
|
||||
@@ -52,7 +108,7 @@ namespace linker.plugins.updater
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(param.Content) || param.Content == config.Data.Client.Id)
|
||||
{
|
||||
updaterTransfer.Exit();
|
||||
Environment.Exit(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
129
linker/plugins/updater/UpdaterClientTransfer.cs
Normal file
129
linker/plugins/updater/UpdaterClientTransfer.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using linker.client;
|
||||
using linker.client.config;
|
||||
using linker.config;
|
||||
using linker.plugins.updater.messenger;
|
||||
using linker.server;
|
||||
using MemoryPack;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace linker.plugins.updater
|
||||
{
|
||||
public sealed class UpdaterClientTransfer
|
||||
{
|
||||
private UpdateInfo updateInfo = new UpdateInfo();
|
||||
private ConcurrentDictionary<string, UpdateInfo> updateInfos = new ConcurrentDictionary<string, UpdateInfo>();
|
||||
|
||||
private readonly FileConfig fileConfig;
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly ClientSignInState clientSignInState;
|
||||
private readonly UpdaterHelper updaterHelper;
|
||||
|
||||
private readonly RunningConfig running;
|
||||
private readonly RunningConfigTransfer runningConfigTransfer;
|
||||
private string configKey = "updater";
|
||||
|
||||
public UpdaterClientTransfer(FileConfig fileConfig, MessengerSender messengerSender, ClientSignInState clientSignInState, UpdaterHelper updaterHelper, RunningConfig running, RunningConfigTransfer runningConfigTransfer)
|
||||
{
|
||||
this.fileConfig = fileConfig;
|
||||
this.messengerSender = messengerSender;
|
||||
this.clientSignInState = clientSignInState;
|
||||
this.updaterHelper = updaterHelper;
|
||||
this.running = running;
|
||||
this.runningConfigTransfer = runningConfigTransfer;
|
||||
|
||||
|
||||
runningConfigTransfer.Setter(configKey, SetSecretKey);
|
||||
runningConfigTransfer.Getter(configKey, () => MemoryPackSerializer.Serialize(GetSecretKey()));
|
||||
clientSignInState.NetworkFirstEnabledHandle += () =>
|
||||
{
|
||||
LoadTask();
|
||||
UpdateTask();
|
||||
};
|
||||
|
||||
}
|
||||
public string GetSecretKey()
|
||||
{
|
||||
return running.Data.UpdaterSecretKey;
|
||||
}
|
||||
public void SetSecretKey(string key)
|
||||
{
|
||||
running.Data.UpdaterSecretKey = key;
|
||||
running.Data.Update();
|
||||
runningConfigTransfer.IncrementVersion(configKey);
|
||||
SyncKey();
|
||||
}
|
||||
private void SetSecretKey(Memory<byte> data)
|
||||
{
|
||||
running.Data.UpdaterSecretKey = MemoryPackSerializer.Deserialize<string>(data.Span);
|
||||
running.Data.Update();
|
||||
}
|
||||
private void SyncKey()
|
||||
{
|
||||
runningConfigTransfer.Sync(configKey, MemoryPackSerializer.Serialize(GetSecretKey()));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 所有客户端的更新信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ConcurrentDictionary<string, UpdateInfo> Get()
|
||||
{
|
||||
return updateInfos;
|
||||
}
|
||||
/// <summary>
|
||||
/// 确认更新
|
||||
/// </summary>
|
||||
public void Confirm(string version)
|
||||
{
|
||||
updaterHelper.Confirm(updateInfo, version);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (updateInfo.StatusChanged())
|
||||
{
|
||||
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 updaterHelper.GetUpdateInfo(updateInfo);
|
||||
await Task.Delay(60000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,10 +1,5 @@
|
||||
using linker.client;
|
||||
using linker.config;
|
||||
using linker.libs;
|
||||
using linker.plugins.updater.messenger;
|
||||
using linker.server;
|
||||
using linker.libs;
|
||||
using MemoryPack;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -12,112 +7,20 @@ using System.Text.RegularExpressions;
|
||||
|
||||
namespace linker.plugins.updater
|
||||
{
|
||||
public sealed class UpdaterTransfer
|
||||
public sealed class UpdaterHelper
|
||||
{
|
||||
private UpdateInfo updateInfo = new UpdateInfo();
|
||||
private ConcurrentDictionary<string, UpdateInfo> updateInfos = new ConcurrentDictionary<string, UpdateInfo>();
|
||||
|
||||
private readonly FileConfig fileConfig;
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly ClientSignInState clientSignInState;
|
||||
public UpdaterTransfer(FileConfig fileConfig, MessengerSender messengerSender, ClientSignInState clientSignInState)
|
||||
private string[] extractExcludeFiles = new string[] { "msquic.dll", "msquic-openssl.dll", "tun2socks", "tun2socks.exe" };
|
||||
public UpdaterHelper()
|
||||
{
|
||||
this.fileConfig = fileConfig;
|
||||
this.messengerSender = messengerSender;
|
||||
this.clientSignInState = clientSignInState;
|
||||
|
||||
clientSignInState.NetworkFirstEnabledHandle += () =>
|
||||
{
|
||||
LoadTask();
|
||||
UpdateTask();
|
||||
};
|
||||
|
||||
StartClearTempFile();
|
||||
ClearFiles();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 所有客户端的更新信息
|
||||
/// 获取更新信息
|
||||
/// </summary>
|
||||
/// <param name="updateInfo"></param>
|
||||
/// <returns></returns>
|
||||
public ConcurrentDictionary<string, UpdateInfo> Get()
|
||||
{
|
||||
return updateInfos;
|
||||
}
|
||||
/// <summary>
|
||||
/// 确认更新
|
||||
/// </summary>
|
||||
public void Confirm(string version)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
string fileName = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName);
|
||||
|
||||
await DownloadUpdate(version);
|
||||
await ExtractUpdate();
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
string result = CommandHelper.Linux(string.Empty, new string[] { $"chmod a+x {fileName}" });
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
string result = CommandHelper.Osx(string.Empty, new string[] { $"chmod a+x {fileName}" });
|
||||
}
|
||||
});
|
||||
}
|
||||
/// <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)
|
||||
{
|
||||
if (updateInfo.StatusChanged())
|
||||
{
|
||||
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()
|
||||
public async Task GetUpdateInfo(UpdateInfo updateInfo)
|
||||
{
|
||||
//正在检查,或者已经确认更新了
|
||||
if (updateInfo.Status == UpdateStatus.Checking || updateInfo.Status > UpdateStatus.Checked)
|
||||
@@ -147,85 +50,13 @@ namespace linker.plugins.updater
|
||||
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)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
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(string version)
|
||||
/// <summary>
|
||||
/// 下载更新
|
||||
/// </summary>
|
||||
/// <param name="updateInfo"></param>
|
||||
/// <param name="version"></param>
|
||||
/// <returns></returns>
|
||||
public async Task DownloadUpdate(UpdateInfo updateInfo, string version)
|
||||
{
|
||||
if (updateInfo.Status != UpdateStatus.Checked)
|
||||
{
|
||||
@@ -277,12 +108,114 @@ namespace linker.plugins.updater
|
||||
updateInfo.Status = status;
|
||||
}
|
||||
}
|
||||
|
||||
private void StartClearTempFile()
|
||||
/// <summary>
|
||||
/// 解压更新
|
||||
/// </summary>
|
||||
/// <param name="updateInfo"></param>
|
||||
/// <returns></returns>
|
||||
public async Task ExtractUpdate(UpdateInfo updateInfo)
|
||||
{
|
||||
ClearTempFile();
|
||||
//没下载完成
|
||||
if (updateInfo.Status != UpdateStatus.Downloaded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
string fileName = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName);
|
||||
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);
|
||||
|
||||
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 (extractExcludeFiles.Contains(Path.GetFileName(entryPath)))
|
||||
{
|
||||
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)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
string result = CommandHelper.Linux(string.Empty, new string[] { $"chmod a+x {fileName}" });
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
string result = CommandHelper.Osx(string.Empty, new string[] { $"chmod a+x {fileName}" });
|
||||
}
|
||||
}
|
||||
private void ClearTempFile(string path = "./")
|
||||
|
||||
public void Confirm(UpdateInfo updateInfo, string version)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await DownloadUpdate(updateInfo, version);
|
||||
await ExtractUpdate(updateInfo);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理旧文件
|
||||
/// </summary>
|
||||
private void ClearFiles()
|
||||
{
|
||||
ClearTempFiles();
|
||||
}
|
||||
private void ClearTempFiles(string path = "./")
|
||||
{
|
||||
string fullPath = Path.GetFullPath(path);
|
||||
|
||||
@@ -298,10 +231,9 @@ namespace linker.plugins.updater
|
||||
}
|
||||
foreach (var item in Directory.GetDirectories(fullPath))
|
||||
{
|
||||
ClearTempFile(item);
|
||||
ClearTempFiles(item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
27
linker/plugins/updater/UpdaterServerTransfer.cs
Normal file
27
linker/plugins/updater/UpdaterServerTransfer.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace linker.plugins.updater
|
||||
{
|
||||
public sealed class UpdaterServerTransfer
|
||||
{
|
||||
private UpdateInfo updateInfo = new UpdateInfo { Status = UpdateStatus.Checked };
|
||||
private readonly UpdaterHelper updaterHelper;
|
||||
public UpdaterServerTransfer(UpdaterHelper updaterHelper)
|
||||
{
|
||||
this.updaterHelper = updaterHelper;
|
||||
}
|
||||
|
||||
public UpdateInfo Get()
|
||||
{
|
||||
return updateInfo;
|
||||
}
|
||||
/// <summary>
|
||||
/// 确认更新
|
||||
/// </summary>
|
||||
public void Confirm(string version)
|
||||
{
|
||||
updaterHelper.Confirm(updateInfo, version);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -23,24 +23,29 @@ namespace linker.plugins.updater
|
||||
|
||||
public void AddClient(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies)
|
||||
{
|
||||
serviceCollection.AddSingleton<UpdaterTransfer>();
|
||||
serviceCollection.AddSingleton<UpdaterHelper>();
|
||||
serviceCollection.AddSingleton<UpdaterClientTransfer>();
|
||||
|
||||
serviceCollection.AddSingleton<UpdaterClientApiController>();
|
||||
serviceCollection.AddSingleton<UpdaterClientMessenger>();
|
||||
serviceCollection.AddSingleton<UpdaterClientApiController>();
|
||||
}
|
||||
|
||||
public void AddServer(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies)
|
||||
{
|
||||
serviceCollection.AddSingleton<UpdaterHelper>();
|
||||
serviceCollection.AddSingleton<UpdaterServerTransfer>();
|
||||
|
||||
serviceCollection.AddSingleton<UpdaterServerMessenger>();
|
||||
}
|
||||
|
||||
public void UseClient(ServiceProvider serviceProvider, FileConfig config, Assembly[] assemblies)
|
||||
{
|
||||
UpdaterTransfer updaterTransfer = serviceProvider.GetService<UpdaterTransfer>();
|
||||
_ = serviceProvider.GetService<UpdaterClientTransfer>();
|
||||
}
|
||||
|
||||
public void UseServer(ServiceProvider serviceProvider, FileConfig config, Assembly[] assemblies)
|
||||
{
|
||||
_ = serviceProvider.GetService<UpdaterServerTransfer>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
using MemoryPack;
|
||||
|
||||
|
||||
namespace linker.client.config
|
||||
{
|
||||
public sealed partial class RunningConfigInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动更新密钥
|
||||
/// </summary>
|
||||
public string UpdaterSecretKey { get; set; } = "snltty";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
namespace linker.plugins.updater.config
|
||||
{
|
||||
[MemoryPackable]
|
||||
@@ -8,4 +22,33 @@ namespace linker.plugins.updater.config
|
||||
public string MachineId { get; set; }
|
||||
public string Version { get; set; }
|
||||
}
|
||||
|
||||
[MemoryPackable]
|
||||
public sealed partial class UpdaterConfirmServerInfo
|
||||
{
|
||||
public string SecretKey { get; set; }
|
||||
public string Version { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace linker.config
|
||||
{
|
||||
public partial class ConfigServerInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 服务器穿透配置
|
||||
/// </summary>
|
||||
public UpdaterConfigServerInfo Updater { get; set; } = new UpdaterConfigServerInfo();
|
||||
}
|
||||
|
||||
public sealed class UpdaterConfigServerInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 密钥
|
||||
/// </summary>
|
||||
public string SecretKey { get; set; } = Guid.NewGuid().ToString().ToUpper();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using linker.plugins.signin.messenger;
|
||||
using linker.config;
|
||||
using linker.plugins.signin.messenger;
|
||||
using linker.plugins.updater.config;
|
||||
using linker.server;
|
||||
using MemoryPack;
|
||||
@@ -7,8 +8,8 @@ namespace linker.plugins.updater.messenger
|
||||
{
|
||||
public sealed class UpdaterClientMessenger : IMessenger
|
||||
{
|
||||
private readonly UpdaterTransfer updaterTransfer;
|
||||
public UpdaterClientMessenger(UpdaterTransfer updaterTransfer)
|
||||
private readonly UpdaterClientTransfer updaterTransfer;
|
||||
public UpdaterClientMessenger(UpdaterClientTransfer updaterTransfer)
|
||||
{
|
||||
this.updaterTransfer = updaterTransfer;
|
||||
}
|
||||
@@ -41,7 +42,7 @@ namespace linker.plugins.updater.messenger
|
||||
[MessengerId((ushort)UpdaterMessengerIds.Exit)]
|
||||
public void Exit(IConnection connection)
|
||||
{
|
||||
updaterTransfer.Exit();
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,12 +51,52 @@ namespace linker.plugins.updater.messenger
|
||||
{
|
||||
private readonly MessengerSender messengerSender;
|
||||
private readonly SignCaching signCaching;
|
||||
private readonly UpdaterServerTransfer updaterServerTransfer;
|
||||
private readonly FileConfig fileConfig;
|
||||
|
||||
public UpdaterServerMessenger(MessengerSender messengerSender, SignCaching signCaching)
|
||||
public UpdaterServerMessenger(MessengerSender messengerSender, SignCaching signCaching, UpdaterServerTransfer updaterServerTransfer, FileConfig fileConfig)
|
||||
{
|
||||
this.messengerSender = messengerSender;
|
||||
this.signCaching = signCaching;
|
||||
this.updaterServerTransfer = updaterServerTransfer;
|
||||
this.fileConfig = fileConfig;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取服务器的更新信息
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
[MessengerId((ushort)UpdaterMessengerIds.UpdateServer)]
|
||||
public void UpdateServer(IConnection connection)
|
||||
{
|
||||
connection.Write(MemoryPackSerializer.Serialize(updaterServerTransfer.Get()));
|
||||
}
|
||||
/// <summary>
|
||||
/// 开始更新服务器
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
[MessengerId((ushort)UpdaterMessengerIds.ConfirmServer)]
|
||||
public void ConfirmServer(IConnection connection)
|
||||
{
|
||||
UpdaterConfirmServerInfo confirm = MemoryPackSerializer.Deserialize<UpdaterConfirmServerInfo>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
if(fileConfig.Data.Server.Updater.SecretKey == confirm.SecretKey)
|
||||
{
|
||||
updaterServerTransfer.Confirm(confirm.Version);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 关闭服务器
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
[MessengerId((ushort)UpdaterMessengerIds.ExitServer)]
|
||||
public void ExitServer(IConnection connection)
|
||||
{
|
||||
UpdaterConfirmServerInfo confirm = MemoryPackSerializer.Deserialize<UpdaterConfirmServerInfo>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
if (fileConfig.Data.Server.Updater.SecretKey == confirm.SecretKey)
|
||||
{
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 转发确认更新消息
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
ExitForward = 2605,
|
||||
Exit = 2606,
|
||||
|
||||
UpdateServer = 2607,
|
||||
ConfirmServer = 2608,
|
||||
ExitServer = 2609,
|
||||
|
||||
Max = 2299
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ do
|
||||
for r in ${rs[@]}
|
||||
do
|
||||
dotnet publish ./${f} -c release -f net8.0 -o ./public/publish/docker/linux-${p}-${r}/${f} -r ${p}-${r} --self-contained true -p:TieredPGO=true -p:DebugType=none -p:DebugSymbols=false -p:PublishSingleFile=true -p:PublishTrimmed=true -p:EnableCompressionInSingleFile=true -p:DebuggerSupport=false -p:EnableUnsafeBinaryFormatterSerialization=false -p:EnableUnsafeUTF7Encoding=false -p:HttpActivityPropagationSupport=false -p:InvariantGlobalization=true -p:MetadataUpdaterSupport=false -p:UseSystemResourceKeys=true -p:TrimMode=partial
|
||||
cp -rf public/publish/docker/linux-${p}-${r}/${f}/${f} public/publish/docker/linux-${p}-${r}/${f}/${f}.run
|
||||
rm -rf public/publish/docker/linux-${p}-${r}/${f}/${f}
|
||||
cp -rf linker/Dockerfile-${p} public/publish/docker/linux-${p}-${r}/${f}/Dockerfile-${p}
|
||||
cp -rf public/extends/any/* public/publish/docker/linux-${p}-${r}/${f}/*
|
||||
|
||||
@@ -15,8 +15,10 @@ for %%r in (win-x64,win-arm64) do (
|
||||
dotnet publish ./linker.service -c release -f net8.0 -o public/extends/%%r/linker-%%r/ -r %%r -p:PublishAot=true -p:PublishTrimmed=true --self-contained true -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 "linker.tray.win\\dist\\*" "public\\extends\\%%r\\linker-%%r\\*" /s /f /h /y
|
||||
echo F|xcopy "linker\\msquic.dll" "public\\extends\\%%r\\linker-%%r\\msquic.dll" /s /f /h /y
|
||||
echo F|xcopy "linker\\msquic-openssl.dll" "public\\extends\\%%r\\linker-%%r\\msquic-openssl.dll" /s /f /h /y
|
||||
echo F|xcopy "linker\\msquic-openssl3-x64.dll" "public\\extends\\%%r\\linker-%%r\\msquic-openssl.dll" /s /f /h /y)
|
||||
)
|
||||
echo F|xcopy "linker\\msquic-arm64.dll" "public\\extends\\win-arm64\\linker-win-arm64\\msquic.dll" /s /f /h /y
|
||||
echo F|xcopy "linker\\msquic-openssl3-arm64.dll" "public\\extends\\win-arm64\\linker-win-arm64\\msquic-openssl.dll" /s /f /h /y
|
||||
|
||||
for %%r in (linux-x64,linux-arm64,osx-x64,osx-arm64) do (
|
||||
echo F|xcopy "linker\\plugins\\tuntap\\tun2socks-%%r" "public\\extends\\%%r\\linker-%%r\\plugins\\tuntap\\tun2socks" /s /f /h /y
|
||||
|
||||
Reference in New Issue
Block a user