This commit is contained in:
snltty
2024-09-04 17:41:53 +08:00
parent 453ca1f6f2
commit 3824e514bb
21 changed files with 283 additions and 225 deletions

View File

@@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

View File

@@ -7,7 +7,6 @@ using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Net;
using linker.tunnel.wanport;
using System.Collections.Generic;
namespace linker.tunnel
{
@@ -186,8 +185,11 @@ namespace linker.tunnel
LoggerHelper.Instance.Error($"tunnel {transport.Name} get remote {remoteMachineId} external ip fail ");
break;
}
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel {transport.Name} got local external ip {localInfo.Result.ToJson()}");
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel {transport.Name} got remote external ip {remoteInfo.Result.ToJson()}");
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel {transportItem.ToJson()}");
@@ -332,12 +334,12 @@ namespace linker.tunnel
private void OnConnecting(TunnelTransportInfo tunnelTransportInfo)
{
//if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel connecting {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName}");
}
private void OnConnectBegin(TunnelTransportInfo tunnelTransportInfo)
{
//if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel connecting from {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName}");
}
@@ -347,7 +349,7 @@ namespace linker.tunnel
/// <param name="connection"></param>
private void _OnConnected(ITunnelConnection connection)
{
//if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Debug($"tunnel connect {connection.RemoteMachineId}->{connection.RemoteMachineName} success->{connection.IPEndPoint}");
//调用以下别人注册的回调
@@ -368,6 +370,7 @@ namespace linker.tunnel
}
private void OnConnectFail(TunnelTransportInfo tunnelTransportInfo)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error($"tunnel connect {tunnelTransportInfo.Remote.MachineId} fail");
}
@@ -425,6 +428,7 @@ namespace linker.tunnel
{
if (AddBackground(remoteMachineId, transactionId) == false)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error($"tunnel background {remoteMachineId}@{transactionId} already exists");
return;
}

View File

@@ -308,6 +308,8 @@ namespace linker.tunnel.transport
await targetSocket.SendToAsync($"{flagTexts}-{tunnelTransportInfo.Local.MachineId}-{tunnelTransportInfo.FlowId}".ToBytes(), ep).ConfigureAwait(false);
await targetSocket.ReceiveFromAsync(new byte[1024], new IPEndPoint(IPAddress.Any, 0)).WaitAsync(TimeSpan.FromMilliseconds(500)).ConfigureAwait(false);
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Debug($"{Name} connect to {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName} {ep} success");
TunnelConnectionUdp result = new TunnelConnectionUdp

View File

@@ -7,3 +7,6 @@ export const getConfig = () => {
export const install = (data) => {
return sendWebsocketMsg('configclient/install', data);
}
export const exportConfig = () => {
return sendWebsocketMsg('configclient/export');
}

View File

@@ -1,6 +1,5 @@
<template>
<div v-if="config" class="status-api-wrap" :class="{connected:connected}">
<template >
<el-popconfirm confirm-button-text="清除" cancel-button-text="更改" title="确定你的操作?" @cancel="handleShow" @confirm="handleResetConnect" >
<template #reference>
<a href="javascript:;" title="此设备的管理接口">
@@ -9,7 +8,6 @@
</a>
</template>
</el-popconfirm>
</template>
<el-dialog class="options-center" title="管理接口" destroy-on-close v-model="showPort" center :show-close="false"
:close-on-click-modal="false" align-center width="200">
<div class="port-wrap t-c">

View File

@@ -33,6 +33,7 @@
</div>
<div class="flex-1"></div>
<div class="share"><Share :config="config"></Share></div>
<div class="api"><Api :config="config"></Api></div>
<div class="server" ><Server :config="config"></Server></div>
@@ -48,12 +49,13 @@
import { computed, h, reactive, ref } from 'vue';
import Api from './Api.vue'
import Server from './Server.vue'
import Share from './Share.vue'
import { injectGlobalData } from '@/provide';
import {Download,Loading,CircleCheck} from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox,ElSelect,ElOption } from 'element-plus';
import { confirm, exit } from '@/apis/updater';
export default {
components:{Api,Server,Download,Loading,CircleCheck},
components:{Api,Server,Share,Download,Loading,CircleCheck},
props:['config'],
setup(props) {
@@ -92,6 +94,7 @@ export default {
? 'yellow' :'green'
});
const handleUpdate = ()=>{
if(!props.config) return;
if(!updater.value.Version){
ElMessage.error('未检测到更新');
return;

View File

@@ -0,0 +1,63 @@
<template>
<div v-if="config" class="status-share-wrap">
<a href="javascript:;" title="此设备的管理接口" @click="state.show = true">
<el-icon size="16"><Share /></el-icon>
导出配置
</a>
<el-dialog class="options-center" title="导出配置" destroy-on-close v-model="state.show" center width="300" top="1vh">
<div class="port-wrap t-c">
导出配置作为节点客户端运行其仅有查看基本信息的能力无法修改任何配置如果使用docker可以仅复制configs文件夹过去docker映射配置文件夹即可
</div>
<template #footer>
<el-button plain @click="state.show = false" :loading="state.loading">取消</el-button>
<el-button type="success" plain @click="handleExport" :loading="state.loading">确定导出</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { reactive } from 'vue';
import {Share} from '@element-plus/icons-vue'
import { exportConfig } from '@/apis/config';
import { ElMessage } from 'element-plus';
export default {
components:{Share},
props:['config'],
setup(props) {
const state = reactive({
show: false,
loading:false
});
const handleExport = ()=>{
state.loading = true;
exportConfig().then(()=>{
state.loading = false;
state.show = false;
ElMessage.success('导出成功');
const link = document.createElement('a');
link.download = 'client-node-export.zip';
link.href = '/client-node-export.zip';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}).catch(()=>{
state.loading = false;
});
}
return {config:props.config, state,handleExport};
}
}
</script>
<style lang="stylus" scoped>
.status-share-wrap{
padding-right:2rem;
a{color:#333;}
.el-icon{
vertical-align:text-top;
}
}
</style>

View File

@@ -83,7 +83,7 @@ export default {
const machineId = computed(() => globalData.value.config.Client.Id);
const showDelay = computed(()=>((globalData.value.config.Running.Tuntap || {Switch:0}).Switch & 2) == 2);
const handleTuntap = (tuntap) => {
if(!props.config && machineId.value != tuntap.MachineId){
if(!props.config){
return;
}
const fn = tuntap.running ? stopTuntap (tuntap.MachineId) : runTuntap(tuntap.MachineId);

View File

@@ -72,7 +72,7 @@ export default {
? 'yellow' :'green'
})
const handleUpdate = ()=>{
if(!props.config && machineId.value != props.item.MachineId){
if(!props.config){
return;
}
const updateInfo = updater.value.list[props.item.MachineId];

View File

@@ -1,95 +1,39 @@
<template>
<div class="head-wrap">
<div class="tools flex">
<span class="label">服务器 </span><el-select v-model="state.server" placeholder="服务器" style="width:12rem" size="small">
<span class="label">服务器 </span><el-select v-model="state.server" placeholder="服务器" style="width:16rem" size="small">
<el-option v-for="item in state.servers":key="item.Host" :label="item.Name":value="item.Host" ></el-option>
</el-select>
<span class="flex-1"></span>
<el-button size="small" @click="handleEdit">
编辑<el-icon><Edit /></el-icon>
</el-button>
<el-button size="small" @click="handleRefresh">
刷新(F5)<el-icon><Refresh /></el-icon>
</el-button>
</div>
</div>
<el-dialog v-model="state.show" title="设置" width="260">
<div>
<el-form :model="state.form" :rules="state.rules" label-width="7rem">
<el-form-item label="服务器" prop="host">
<el-input v-model="state.form.host"/>
</el-form-item>
<el-form-item label="中继密钥" prop="relaySecretKey">
<el-input v-model="state.form.relaySecretKey" type="password" show-password maxlength="36" show-word-limit />
</el-form-item>
<el-form-item label="分组号" prop="groupid">
<el-input v-model="state.form.groupid" type="password" show-password maxlength="36" show-word-limit />
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer t-c">
<el-button @click="state.show = false" :loading="state.loading">取消</el-button>
<el-button type="primary" @click="handleSave" :loading="state.loading">确定保存</el-button>
</div>
</template>
</el-dialog>
</template>
<script>
import { injectGlobalData } from '@/provide';
import { reactive, watch } from 'vue';
import { Edit,Refresh } from '@element-plus/icons-vue';
import {save} from '@/apis/net'
import { ElMessage } from 'element-plus';
export default {
components:{Edit,Refresh},
setup () {
const globalData = injectGlobalData();
const state = reactive({
server:"linker.snltty.com:1802",
servers:[],
groupid:'snltty',
show:false,
loading:false,
form: {
host: '',
relaySecretKey: '',
groupid: '',
},
rules: {},
servers:[]
});
watch(()=>globalData.value.config.Running.Client.Servers,()=>{
state.servers = (globalData.value.config.Running.Client.Servers || []).slice(0,1);
state.server = globalData.value.config.Client.Server;
state.groupid = globalData.value.config.Client.GroupId;
});
const handleEdit = ()=>{
state.form.host = state.server;
state.form.groupid = state.groupid;
state.form.relaySecretKey = globalData.value.config.Running.Relay.Servers.filter(c=>c.Host == state.form.host)[0].SecretKey;
state.show = true;
}
const handleSave = ()=>{
state.loading = true;
save(state.form).then(()=>{
state.loading = false;
state.show = false;
ElMessage.success('操作成功!');
}).catch(()=>{
state.loading =false;
ElMessage.error('操作失败!');
})
}
const handleRefresh = ()=>{
window.location.reload();
}
return {
state,handleRefresh,handleEdit,handleSave
state,handleRefresh
}
}
}

View File

@@ -7,7 +7,7 @@
<dl>
<dt class="flex">
<div>
<DeviceName @edit="handleDeviceEdit" :item="item"></DeviceName>
<DeviceName :item="item"></DeviceName>
</div>
<div class="flex-1"></div>
<div>
@@ -15,7 +15,7 @@
</div>
</dt>
<dd class="tuntap">
<TuntapShow v-if="tuntap.list[item.MachineId]" @edit="handleTuntapEdit" :item="item"></TuntapShow>
<TuntapShow v-if="tuntap.list[item.MachineId]" :item="item"></TuntapShow>
</dd>
</dl>
</li>
@@ -29,8 +29,6 @@
@current-change="handlePageChange" @size-change="handlePageSizeChange" :page-sizes="[10, 20, 50, 100,255]" />
</div>
</div>
<TuntapEdit v-if="tuntap.showEdit" v-model="tuntap.showEdit" @change="handleTuntapRefresh"></TuntapEdit>
<DeviceEdit v-if="devices.showDeviceEdit" v-model="devices.showDeviceEdit" @change="handlePageChange" :data="devices.deviceInfo"></DeviceEdit>
</div>
</template>
<script>
@@ -43,11 +41,9 @@ import { provideUpdater } from '../full/devices/updater'
import { StarFilled} from '@element-plus/icons-vue'
import UpdaterBtn from '../full/devices/UpdaterBtn.vue'
import DeviceName from '../full/devices/DeviceName.vue'
import DeviceEdit from '../full/devices/DeviceEdit.vue'
import TuntapShow from '../full/devices/TuntapShow.vue';
import TuntapEdit from './TuntapEdit.vue'
export default {
components: {StarFilled,UpdaterBtn,DeviceName,DeviceEdit,TuntapShow,TuntapEdit},
components: {StarFilled,UpdaterBtn,DeviceName,TuntapShow},
setup(props) {
const globalData = injectGlobalData();
@@ -81,8 +77,8 @@ export default {
});
return {
state,devices,handleDeviceEdit, machineId, handlePageChange,handlePageSizeChange, handleDel,
tuntap,handleTuntapEdit,handleTuntapRefresh
state,devices, machineId, handlePageChange,handlePageSizeChange,
tuntap
}
}
}

View File

@@ -1,121 +0,0 @@
<template>
<el-dialog v-model="state.show" :close-on-click-modal="false" title="组网设置" top="1vh" width="270">
<div>
<el-form ref="ruleFormRef" :model="state.ruleForm" :rules="state.rules" label-width="0">
<el-form-item prop="gateway" style="margin-bottom:0">
赐予此设备IP其它设备可通过此IP访问
</el-form-item>
<el-form-item label="" prop="IP" style="margin-bottom:0">
<el-input v-model="state.ruleForm.IP" style="width:14rem" />
<span>/</span>
<el-input @change="handlePrefixLengthChange" v-model="state.ruleForm.PrefixLength" style="width:4rem" />
</el-form-item>
<el-form-item label="" prop="ShowDelay">
<el-checkbox v-model="state.ruleForm.ShowDelay" label="显示延迟" size="large" />
<el-checkbox v-model="state.ruleForm.AutoConnect" label="自动连接?" size="large" />
</el-form-item>
<el-form-item label="" prop="Btns">
<div class="t-c w-100">
<el-button @click="state.show = false">取消</el-button>
<el-button type="primary" @click="handleSave">确认</el-button>
</div>
</el-form-item>
</el-form>
</div>
</el-dialog>
</template>
<script>
import {updateTuntap } from '@/apis/tuntap';
import { injectGlobalData } from '@/provide';
import { ElMessage } from 'element-plus';
import { reactive, ref, watch } from 'vue';
import { useTuntap } from '../full/devices/tuntap';
import { Delete, Plus } from '@element-plus/icons-vue'
export default {
props: ['modelValue'],
emits: ['change','update:modelValue'],
components: {Delete,Plus},
setup(props, { emit }) {
const globalData = injectGlobalData();
const tuntap = useTuntap();
const ruleFormRef = ref(null);
const state = reactive({
show: true,
bufferSize:globalData.value.bufferSize,
ruleForm: {
IP: tuntap.value.current.IP,
LanIPs: tuntap.value.current.LanIPs.slice(0),
Masks: tuntap.value.current.Masks.slice(0),
PrefixLength:tuntap.value.current.PrefixLength || 24,
Gateway: tuntap.value.current.Gateway,
ShowDelay: tuntap.value.current.ShowDelay,
AutoConnect: tuntap.value.current.AutoConnect,
Upgrade: tuntap.value.current.Upgrade,
Forwards:tuntap.value.current.Forwards.length == 0 ? [
{ListenAddr:'0.0.0.0',ListenPort:0,ConnectAddr:'0.0.0.0',ConnectPort:0}
] : tuntap.value.current.Forwards.slice(0)
},
rules: {}
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const handlePrefixLengthChange = ()=>{
var value = +state.ruleForm.PrefixLength;
if(value>32 || value<16 || isNaN(value)){
value = 24;
}
state.ruleForm.PrefixLength = value;
}
const handleSave = () => {
const json = JSON.parse(JSON.stringify(tuntap.value.current));
json.IP = state.ruleForm.IP || '0.0.0.0';
const {lanips,masks} = state.ruleForm.LanIPs.reduce((json,ip,index)=>{
if(ip && state.ruleForm.Masks[index]){
json.lanips.push(ip);
json.masks.push(state.ruleForm.Masks[index]);
}
return json;
},{lanips:[],masks:[]});
json.LanIPs = lanips;
json.Masks = masks;
json.PrefixLength = +state.ruleForm.PrefixLength;
json.Gateway = state.ruleForm.Gateway;
json.ShowDelay = state.ruleForm.ShowDelay;
json.AutoConnect = state.ruleForm.AutoConnect;
json.Upgrade = state.ruleForm.Upgrade;
json.Forwards = state.ruleForm.Forwards;
json.Forwards.forEach(c=>{
c.ListenPort=+c.ListenPort;
c.ConnectPort=+c.ConnectPort;
});
updateTuntap(json).then(() => {
state.show = false;
ElMessage.success('已操作!');
emit('change')
}).catch(() => {
ElMessage.error('操作失败!');
});
}
return {
state, ruleFormRef,handlePrefixLengthChange, handleSave
}
}
}
</script>
<style lang="stylus" scoped>
.el-switch.is-disabled{opacity :1;}
.upgrade-wrap{
border:1px solid #ddd;
margin-bottom:2rem
padding:0 0 1rem 0;
}
</style>

View File

@@ -37,12 +37,14 @@ namespace linker.config
object property = item.GetValue(Data);
MethodInfo method = property.GetType().GetMethod("Load");
MethodInfo method1 = property.GetType().GetMethod("Set");
fsDic.Add(item.Name.ToLower(), new FileReadWrite
{
Path = Path.Join(configPath, $"{item.Name.ToLower()}.json"),
Property = item,
PropertyObject = property,
PropertyLoadMethod = method
PropertyLoadMethod = method,
PropertySetMethod = method1,
});
}
}
@@ -89,7 +91,16 @@ namespace linker.config
{
continue;
}
File.WriteAllText(item.Value.Path, item.Value.Property.GetValue(Data).ToJsonFormat());
string text = string.Empty;
if (item.Value.PropertySetMethod != null)
{
text = item.Value.PropertySetMethod.Invoke(item.Value.PropertyObject, new object[] { item.Value.Property.GetValue(Data) }).ToString();
}
else
{
text = item.Value.Property.GetValue(Data).ToJsonFormat();
}
File.WriteAllText(item.Value.Path, text);
}
}
catch (Exception ex)
@@ -126,6 +137,7 @@ namespace linker.config
public PropertyInfo Property { get; set; }
public object PropertyObject { get; set; }
public MethodInfo PropertyLoadMethod { get; set; }
public MethodInfo PropertySetMethod { get; set; }
}
@@ -176,7 +188,9 @@ namespace linker.config
}
public int LoggerSize { get; set; } = 100;
[JsonIgnore]
public string[] IncludePlugins { get; set; } = Array.Empty<string>();
[JsonIgnore]
public string[] ExcludePlugins { get; set; } = Array.Empty<string>();

View File

@@ -29,10 +29,11 @@ namespace linker.plugins.capi
clientServer.LoadPlugins(assemblies);
clientServer.Websocket(config.Data.Client.CApi.ApiPort, config.Data.Client.CApi.ApiPassword);
LoggerHelper.Instance.Warning($"client api listen:{config.Data.Client.CApi.ApiPort}");
if (config.Data.Client.OnlyNode == false)
LoggerHelper.Instance.Warning($"client api password:{config.Data.Client.CApi.ApiPassword}");
}
if (config.Data.Client.CApi.WebPort > 0)
if (config.Data.Client.CApi.WebPort > 0 && config.Data.Client.OnlyNode == false)
{
IWebClientServer webServer = serviceProvider.GetService<IWebClientServer>();
webServer.Start(config.Data.Client.CApi.WebPort, config.Data.Client.CApi.WebRoot);

View File

@@ -4,6 +4,8 @@ using Microsoft.Extensions.DependencyInjection;
using System.Reflection;
using linker.config;
using linker.plugins.client.args;
using System.Net;
using linker.libs.extends;
namespace linker.plugins.client
{
@@ -20,6 +22,11 @@ namespace linker.plugins.client
public void AddClient(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies)
{
if (string.IsNullOrWhiteSpace(config.Data.Client.Name))
{
config.Data.Client.Name = Dns.GetHostName().SubStr(0, 12);
}
serviceCollection.AddSingleton<SignInArgsTransfer>();
serviceCollection.AddSingleton<ClientSignInState>();

View File

@@ -1,8 +1,10 @@
using linker.config;
using linker.libs;
using linker.libs.extends;
using LiteDB;
using MemoryPack;
using System.Net;
using System.Text;
namespace linker.client.config
{
@@ -41,6 +43,13 @@ namespace linker.config
public sealed partial class ConfigClientInfo
{
private ICrypto crypto;
public ConfigClientInfo()
{
crypto = CryptoFactory.CreateSymmetric(Helper.GlobalString);
}
public bool OnlyNode { get; set; }
#if DEBUG
public string Server { get; set; } = new IPEndPoint(IPAddress.Loopback, 1802).ToString();
@@ -83,9 +92,18 @@ namespace linker.config
public string Password { get; set; } = "oeq9tw1o";
public ConfigClientInfo Load(string text)
{
if (text.Contains("ApiPassword"))
{
return text.DeJson<ConfigClientInfo>();
}
return Encoding.UTF8.GetString(crypto.Decode(Convert.FromBase64String(text)).ToArray()).DeJson<ConfigClientInfo>();
}
public string Set(object obj)
{
return Convert.ToBase64String(crypto.Encode(Encoding.UTF8.GetBytes(obj.ToJson())));
}
}

View File

@@ -3,6 +3,8 @@ using linker.libs.api;
using linker.libs.extends;
using linker.client.config;
using linker.plugins.capi;
using System.IO.Compression;
using linker.libs;
namespace linker.plugins.config
{
@@ -74,6 +76,101 @@ namespace linker.plugins.config
return true;
}
public bool Export(ApiControllerParamsInfo param)
{
try
{
string dirName = "client-node-export";
string rootPath = Path.GetFullPath($"./web/{dirName}");
string zipPath = Path.GetFullPath($"./web/{dirName}.zip");
try
{
File.Delete(zipPath);
}
catch (Exception)
{
}
DeleteDirectory(rootPath);
CopyDirectory(Path.GetFullPath("./"), rootPath, dirName);
DeleteDirectory(Path.Combine(rootPath, $"configs"));
string configPath = Path.Combine(rootPath, $"configs");
Directory.CreateDirectory(configPath);
ConfigClientInfo client = config.Data.Client.ToJson().DeJson<ConfigClientInfo>();
client.Name = string.Empty;
client.Id = string.Empty;
client.CApi.WebPort = 0;
client.OnlyNode = true;
File.WriteAllText(Path.Combine(configPath, $"client.json"), client.Set(client));
ConfigCommonInfo common = config.Data.Common.ToJson().DeJson<ConfigCommonInfo>();
common.Install = true;
common.Modes = ["client"];
File.WriteAllText(Path.Combine(configPath, $"common.json"), common.ToJsonFormat());
ZipFile.CreateFromDirectory(rootPath, zipPath);
Task.Run(async () =>
{
await Task.Delay(5000);
try
{
File.Delete(zipPath);
}
catch (Exception)
{
}
DeleteDirectory(rootPath);
});
}
catch (Exception ex)
{
LoggerHelper.Instance.Error(ex);
}
return true;
}
private void DeleteDirectory(string sourceDir)
{
if (Directory.Exists(sourceDir) == false) return;
foreach (string file in Directory.GetFiles(sourceDir))
{
File.Delete(Path.Combine(sourceDir, file));
}
foreach (string subDir in Directory.GetDirectories(sourceDir))
{
DeleteDirectory(Path.Combine(sourceDir, subDir));
}
Directory.Delete(sourceDir);
}
private void CopyDirectory(string sourceDir, string destDir, string excludeDir)
{
// 创建目标目录
Directory.CreateDirectory(destDir);
// 复制所有文件
foreach (string file in Directory.GetFiles(sourceDir))
{
string fileName = Path.GetFileName(file);
string destFile = Path.Combine(destDir, fileName);
File.Copy(file, destFile, true); // true 表示如果目标文件已存在则覆盖
}
// 递归复制所有子目录
foreach (string subDir in Directory.GetDirectories(sourceDir))
{
if (subDir.EndsWith(excludeDir)) continue;
string subDirName = Path.GetFileName(subDir);
string destSubDir = Path.Combine(destDir, subDirName);
CopyDirectory(subDir, destSubDir, excludeDir);
}
}
}
public sealed class ConfigInstallInfo

View File

@@ -20,6 +20,10 @@ namespace linker.plugins.logger
public void AddClient(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies)
{
serviceCollection.AddSingleton<LoggerClientApiController>();
if (config.Data.Client.OnlyNode)
{
config.Data.Common.LoggerType = libs.LoggerTypes.WARNING;
}
}
public void AddServer(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies)
@@ -30,6 +34,7 @@ namespace linker.plugins.logger
public void UseClient(ServiceProvider serviceProvider, FileConfig config, Assembly[] assemblies)
{
LoggerClientApiController logger = serviceProvider.GetService<LoggerClientApiController>();
}
public void UseServer(ServiceProvider serviceProvider, FileConfig config, Assembly[] assemblies)

View File

@@ -10,6 +10,7 @@ using System.Net;
using System.Reflection;
using MemoryPack;
using linker.plugins.client;
using System.Net.Sockets;
namespace linker.plugins.relay
{
@@ -180,7 +181,7 @@ namespace linker.plugins.relay
SecretKey = item.SecretKey,
Server = server,
TransactionId = transactionId,
TransportName = transport.Name,
TransportName = $"{transport.Name}|{item.Name}",
SSL = item.SSL
};
@@ -223,6 +224,7 @@ namespace linker.plugins.relay
try
{
await TestServer(relayInfo);
ITransport _transports = transports.FirstOrDefault(c => c.Name == relayInfo.TransportName);
if (_transports != null)
{
@@ -255,6 +257,29 @@ namespace linker.plugins.relay
}
return false;
}
private async Task TestServer(RelayInfo relayInfo)
{
string[] arr = relayInfo.TransportName.Split('|');
relayInfo.TransportName = arr[0];
try
{
Socket socket = new Socket(relayInfo.Server.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(relayInfo.Server).WaitAsync(TimeSpan.FromMilliseconds(500)).ConfigureAwait(false);
socket.SafeClose();
}
catch (Exception)
{
RelayServerInfo server = null;
if (arr.Length > 1)
{
server = running.Data.Relay.Servers.FirstOrDefault(c => c.Name == arr[1]);
}
server ??= running.Data.Relay.Servers.FirstOrDefault();
relayInfo.Server = NetworkHelper.GetEndPoint(server.Host, 3478);
}
}
/// <summary>
/// 回调
/// </summary>

View File

@@ -6,7 +6,6 @@ using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
namespace linker.plugins.updater
{
@@ -143,6 +142,7 @@ namespace linker.plugins.updater
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('/'))));

View File

@@ -1,3 +1,3 @@
v1.2.9
2024-09-03 17:06:02
2024-09-04 17:41:53
1. 选择是否自动同步配置文件