diff --git a/linker.tunnel/TunnelTransfer.cs b/linker.tunnel/TunnelTransfer.cs index 06ddbb78..8d959461 100644 --- a/linker.tunnel/TunnelTransfer.cs +++ b/linker.tunnel/TunnelTransfer.cs @@ -118,10 +118,8 @@ namespace linker.tunnel /// public async Task ConnectAsync(string remoteMachineId, string transactionId) { - if (connectingDic.TryAdd(remoteMachineId, true) == false) - { - return null; - } + if (connectingDic.TryAdd(remoteMachineId, true) == false) return null; + if (IsBackground(remoteMachineId, transactionId)) return null; try { @@ -221,7 +219,6 @@ namespace linker.tunnel { connectingDic.TryRemove(remoteMachineId, out _); } - return null; } /// @@ -393,5 +390,54 @@ namespace linker.tunnel tunnelTransportInfo.RemoteEndPoints = eps; } + + + + private ConcurrentDictionary backgroundDic = new ConcurrentDictionary(); + public void StartBackground(string remoteMachineId, string transactionId) + { + if (IsBackground(remoteMachineId, transactionId)) return; + AddBackground(remoteMachineId, transactionId); + Task.Run(async () => + { + try + { + for (int i = 0; i < 10; i++) + { + await Task.Delay(3000); + + ITunnelConnection connection = await ConnectAsync(remoteMachineId, transactionId); + if (connection != null) + { + break; + } + } + } + catch (Exception) + { + } + finally + { + RemoveBackground(remoteMachineId, transactionId); + } + }); + + } + private void AddBackground(string remoteMachineId, string transactionId) + { + backgroundDic.TryAdd(GetBackgroundKey(remoteMachineId, transactionId), true); + } + private void RemoveBackground(string remoteMachineId, string transactionId) + { + backgroundDic.TryRemove(GetBackgroundKey(remoteMachineId, transactionId), out _); + } + private bool IsBackground(string remoteMachineId, string transactionId) + { + return backgroundDic.ContainsKey(GetBackgroundKey(remoteMachineId, transactionId)); + } + private string GetBackgroundKey(string remoteMachineId, string transactionId) + { + return $"{remoteMachineId}@{transactionId}"; + } } } diff --git a/linker.tunnel/proxy/TunnelProxy.cs b/linker.tunnel/proxy/TunnelProxy.cs index 067a95aa..059b3215 100644 --- a/linker.tunnel/proxy/TunnelProxy.cs +++ b/linker.tunnel/proxy/TunnelProxy.cs @@ -277,6 +277,15 @@ namespace linker.tunnel.proxy { GC.Collect(); } + + public ConnectId GetTcpConnectId() + { + return new ConnectId(Proxy.ConnectId, Connection.RemoteMachineId.GetHashCode(), Connection.TransactionId.GetHashCode(), (byte)Proxy.Direction); + } + public ConnectIdUdp GetUdpConnectId() + { + return new ConnectIdUdp(Proxy.ConnectId, Proxy.SourceEP, Connection.RemoteMachineId.GetHashCode(), Connection.TransactionId.GetHashCode()); + } } } diff --git a/linker.tunnel/proxy/TunnelProxyTcp.cs b/linker.tunnel/proxy/TunnelProxyTcp.cs index aa1ceb9d..5e8e1bc9 100644 --- a/linker.tunnel/proxy/TunnelProxyTcp.cs +++ b/linker.tunnel/proxy/TunnelProxyTcp.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; +using System.Collections.Generic; namespace linker.tunnel.proxy { @@ -106,7 +107,7 @@ namespace linker.tunnel.proxy } token.Proxy.Step = ProxyStep.Forward; //绑定 - tcpConnections.TryAdd(new ConnectId(token.Proxy.ConnectId, token.Connection.GetHashCode(), (byte)ProxyDirection.Reverse), token); + tcpConnections.TryAdd(token.GetConnectId(ProxyDirection.Reverse), token); } else if (closeConnect) { @@ -253,12 +254,12 @@ namespace linker.tunnel.proxy token.Socket.EndConnect(result); token.Socket.KeepAlive(); - + if (state.Data.Length > 0) { await token.Socket.SendAsync(state.Data.AsMemory(0, state.Length), SocketFlags.None).ConfigureAwait(false); } - tcpConnections.TryAdd(new ConnectId(token.Proxy.ConnectId, token.Connection.GetHashCode(), (byte)ProxyDirection.Forward), token); + tcpConnections.TryAdd(token.GetConnectId(ProxyDirection.Forward), token); await SendToConnection(token).ConfigureAwait(false); token.Proxy.Step = ProxyStep.Forward; @@ -286,7 +287,7 @@ namespace linker.tunnel.proxy { if (tunnelToken.Proxy.Protocol == ProxyProtocol.Tcp) { - ConnectId connectId = new ConnectId(tunnelToken.Proxy.ConnectId, tunnelToken.Connection.GetHashCode(), (byte)tunnelToken.Proxy.Direction); + ConnectId connectId = tunnelToken.GetTcpConnectId(); if (tcpConnections.TryGetValue(connectId, out AsyncUserToken token)) { _ = ProcessReceive(token); @@ -301,7 +302,7 @@ namespace linker.tunnel.proxy { if (tunnelToken.Proxy.Protocol == ProxyProtocol.Tcp) { - ConnectId connectId = new ConnectId(tunnelToken.Proxy.ConnectId, tunnelToken.Connection.GetHashCode(), (byte)tunnelToken.Proxy.Direction); + ConnectId connectId = tunnelToken.GetTcpConnectId(); if (tcpConnections.TryRemove(connectId, out AsyncUserToken token)) { CloseClientSocket(token); @@ -316,7 +317,7 @@ namespace linker.tunnel.proxy /// private async Task SendToSocketTcp(AsyncUserTunnelToken tunnelToken) { - ConnectId connectId = new ConnectId(tunnelToken.Proxy.ConnectId, tunnelToken.Connection.GetHashCode(), (byte)tunnelToken.Proxy.Direction); + ConnectId connectId = tunnelToken.GetTcpConnectId(); if (tunnelToken.Proxy.Step == ProxyStep.Close || tunnelToken.Proxy.Data.Length == 0) { if (tcpConnections.TryRemove(connectId, out AsyncUserToken token)) @@ -329,6 +330,7 @@ namespace linker.tunnel.proxy { try { + token1.Connection = tunnelToken.Connection; await token1.Socket.SendAsync(tunnelToken.Proxy.Data, SocketFlags.None).AsTask().WaitAsync(TimeSpan.FromMilliseconds(5000)).ConfigureAwait(false); } catch (Exception ex) @@ -348,14 +350,15 @@ namespace linker.tunnel.proxy if (token == null) return; if (token.Connection != null) { - tcpConnections.TryRemove(new ConnectId(token.Proxy.ConnectId, token.Connection.GetHashCode(), (byte)token.Proxy.Direction), out _); + tcpConnections.TryRemove(token.GetConnectId(), out _); } token.Clear(); } private void CloseClientSocketTcp(ITunnelConnection connection) { - int hashcode = connection.GetHashCode(); - var tokens = tcpConnections.Where(c => c.Key.hashcode == hashcode).ToList(); + int hashcode1 = connection.RemoteMachineId.GetHashCode(); + int hashcode2 = connection.TransactionId.GetHashCode(); + var tokens = tcpConnections.Where(c => c.Key.hashcode1 == hashcode1 && c.Key.hashcode2 == hashcode2).ToList(); foreach (var item in tokens) { try @@ -407,23 +410,25 @@ namespace linker.tunnel.proxy { public bool Equals(ConnectId x, ConnectId y) { - return x.connectId == y.connectId && x.hashcode == y.hashcode && x.direction == y.direction; + return x.connectId == y.connectId && x.hashcode1 == y.hashcode1 && x.hashcode2 == y.hashcode2 && x.direction == y.direction; } public int GetHashCode(ConnectId obj) { - return obj.connectId.GetHashCode() ^ obj.hashcode ^ obj.direction; + return obj.connectId.GetHashCode() ^ obj.hashcode1 ^ obj.hashcode2 ^ obj.direction; } } public record struct ConnectId { public ulong connectId; - public int hashcode; + public int hashcode1; + public int hashcode2; public byte direction; - public ConnectId(ulong connectId, int hashcode, byte direction) + public ConnectId(ulong connectId, int hashcode1, int hashcode2, byte direction) { this.connectId = connectId; - this.hashcode = hashcode; + this.hashcode1 = hashcode1; + this.hashcode2 = hashcode2; this.direction = direction; } } @@ -450,6 +455,15 @@ namespace linker.tunnel.proxy GC.Collect(); } + + public ConnectId GetConnectId() + { + return new ConnectId(Proxy.ConnectId, Connection.RemoteMachineId.GetHashCode(), Connection.TransactionId.GetHashCode(), (byte)Proxy.Direction); + } + public ConnectId GetConnectId(ProxyDirection proxyDirection) + { + return new ConnectId(Proxy.ConnectId, Connection.RemoteMachineId.GetHashCode(), Connection.TransactionId.GetHashCode(), (byte)proxyDirection); + } } public sealed class ConnectState { diff --git a/linker.tunnel/proxy/TunnelProxyUdp.cs b/linker.tunnel/proxy/TunnelProxyUdp.cs index cffc2aec..fe1258e3 100644 --- a/linker.tunnel/proxy/TunnelProxyUdp.cs +++ b/linker.tunnel/proxy/TunnelProxyUdp.cs @@ -153,7 +153,7 @@ namespace linker.tunnel.proxy if (tunnelToken.Proxy.Direction == ProxyDirection.Forward) { - ConnectIdUdp connectId = new ConnectIdUdp(tunnelToken.Proxy.ConnectId, tunnelToken.Proxy.SourceEP, tunnelToken.Connection.GetHashCode()); + ConnectIdUdp connectId = tunnelToken.GetUdpConnectId(); try { IPEndPoint target = new IPEndPoint(tunnelToken.Proxy.TargetEP.Address, tunnelToken.Proxy.TargetEP.Port); @@ -185,6 +185,7 @@ namespace linker.tunnel.proxy { try { + asyncUserUdpToken.Connection = tunnelToken.Connection; if (await ConnectionReceiveUdp(tunnelToken, asyncUserUdpToken).ConfigureAwait(false) == false) { await asyncUserUdpToken.SourceSocket.SendToAsync(tunnelToken.Proxy.Data, tunnelToken.Proxy.SourceEP).ConfigureAwait(false); @@ -207,7 +208,7 @@ namespace linker.tunnel.proxy await socket.SendToAsync(tunnelToken.Proxy.Data, target).ConfigureAwait(false); - ConnectIdUdp connectId = new ConnectIdUdp(tunnelToken.Proxy.ConnectId, tunnelToken.Proxy.SourceEP, tunnelToken.Connection.GetHashCode()); + ConnectIdUdp connectId = tunnelToken.GetUdpConnectId(); AsyncUserUdpTokenTarget udpToken = new AsyncUserUdpTokenTarget { Proxy = new ProxyInfo @@ -270,8 +271,8 @@ namespace linker.tunnel.proxy /// private async Task SendToConnection(AsyncUserUdpTokenTarget token) { - // SemaphoreSlim semaphoreSlim = token.Proxy.Direction == ProxyDirection.Forward ? semaphoreSlimForward : semaphoreSlimReverse; - // await semaphoreSlim.WaitAsync(); + // SemaphoreSlim semaphoreSlim = token.Proxy.Direction == ProxyDirection.Forward ? semaphoreSlimForward : semaphoreSlimReverse; + // await semaphoreSlim.WaitAsync(); byte[] connectData = token.Proxy.ToBytes(out int length); @@ -290,7 +291,7 @@ namespace linker.tunnel.proxy finally { token.Proxy.Return(connectData); - // semaphoreSlim.Release(); + // semaphoreSlim.Release(); } } @@ -325,8 +326,9 @@ namespace linker.tunnel.proxy private void CloseClientSocketUdp(ITunnelConnection connection) { - int hashcode = connection.GetHashCode(); - var tokens = udpConnections.Where(c => c.Key.hashcode == hashcode).ToList(); + int hashcode1 = connection.RemoteMachineId.GetHashCode(); + int hashcode2 = connection.TransactionId.GetHashCode(); + var tokens = udpConnections.Where(c => c.Key.hashcode1 == hashcode1 && c.Key.hashcode2 == hashcode2).ToList(); foreach (var item in tokens) { try @@ -437,25 +439,27 @@ namespace linker.tunnel.proxy { public bool Equals(ConnectIdUdp x, ConnectIdUdp y) { - return x.source != null && x.source.Equals(y.source) && x.connectId == y.connectId && x.hashcode == y.hashcode; + return x.source != null && x.source.Equals(y.source) && x.connectId == y.connectId && x.hashcode1 == y.hashcode1 && x.hashcode2 == y.hashcode2; } public int GetHashCode(ConnectIdUdp obj) { if (obj.source == null) return 0; - return obj.source.GetHashCode() ^ obj.connectId.GetHashCode() ^ obj.hashcode; + return obj.source.GetHashCode() ^ obj.connectId.GetHashCode() ^ obj.hashcode1 ^ obj.hashcode2; } } public readonly struct ConnectIdUdp { public readonly IPEndPoint source { get; } public readonly ulong connectId { get; } - public int hashcode { get; } + public int hashcode1 { get; } + public int hashcode2 { get; } - public ConnectIdUdp(ulong connectId, IPEndPoint source, int hashcode) + public ConnectIdUdp(ulong connectId, IPEndPoint source, int hashcode1, int hashcode2) { this.connectId = connectId; this.source = source; - this.hashcode = hashcode; + this.hashcode1 = hashcode1; + this.hashcode2 = hashcode2; } } } diff --git a/linker.updater/Program.cs b/linker.updater/Program.cs index 5fe85c79..1ce7747a 100644 --- a/linker.updater/Program.cs +++ b/linker.updater/Program.cs @@ -1,7 +1,10 @@ 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 { @@ -13,17 +16,36 @@ namespace linker.updater await Helper.Await(); } - + static string rootPath = "./updater"; static void Updater() { Task.Factory.StartNew(async () => { while (true) { - UpdateInfo updateInfo = GetUpdateInfo(); - if (updateInfo != null) + try + { + UpdateInfo updateInfo = GetUpdateInfo(); + if (updateInfo != null) + { + if (NeedDownload(updateInfo)) + { + await DownloadUpdate(updateInfo); + } + } + } + catch (Exception) + { + } + try + { + if (NeedExtract()) + { + ExtractUpdate(); + } + } + catch (Exception) { - } await Task.Delay(15000); @@ -32,7 +54,75 @@ namespace linker.updater }, TaskCreationOptions.LongRunning); } + static bool NeedExtract() + { + try + { + return File.Exists(Path.Join(rootPath, "version.txt")) + && File.Exists(Path.Join(rootPath,"updater.zip")) + && File.Exists(Path.Join(rootPath, "extract.txt")) + && File.ReadAllText(Path.Join(rootPath, "version.txt")) != $"v{FileVersionInfo.GetVersionInfo("linker.exe").FileVersion}"; + } + catch (Exception) + { + } + return false; + } + static void ExtractUpdate() + { + try + { + ZipFile.ExtractToDirectory(Path.Join(rootPath, "updater.zip"), "./", Encoding.UTF8, true); + File.Delete(Path.Join(rootPath, "version.txt")); + File.Delete(Path.Join(rootPath, "msg.txt")); + File.Delete(Path.Join(rootPath, "extract.txt")); + File.Delete(Path.Join(rootPath, "updater.zip")); + } + catch (Exception) + { + } + } + + static bool NeedDownload(UpdateInfo updateInfo) + { + try + { + return true; + return (File.Exists(Path.Join(rootPath, "version.txt")) == false + || File.ReadAllText(Path.Join(rootPath, "version.txt")) != updateInfo.Tag) + && $"v{FileVersionInfo.GetVersionInfo("linker.exe").FileVersion}" != updateInfo.Tag; + } + 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(); + + File.WriteAllText(Path.Join(rootPath, "version.txt"), updateInfo.Tag); + File.WriteAllText(Path.Join(rootPath, "msg.txt"), updateInfo.Msg); + } + catch (Exception) + { + } + } static UpdateInfo GetUpdateInfo() { try diff --git a/linker.updater/linker.updater.csproj b/linker.updater/linker.updater.csproj index 69e1f23d..8851a610 100644 --- a/linker.updater/linker.updater.csproj +++ b/linker.updater/linker.updater.csproj @@ -41,6 +41,7 @@ + diff --git a/linker.web/src/components/install/Index.vue b/linker.web/src/components/install/Index.vue index d8990ba3..b74a1e53 100644 --- a/linker.web/src/components/install/Index.vue +++ b/linker.web/src/components/install/Index.vue @@ -1,54 +1,106 @@ - + + + + + - - - - - - - - - - + + + 客户端 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 自动重启 - 手动重启 - - - + + + + + + + 服务端 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -73,13 +125,20 @@ export default { show: false, form: { client: true, - server: false, + server: true, restart: false, name: '', groupid: '', api: 0, web: 0, - password: '' + password: '', + + relayKey:'', + sforwardKey:'', + servicePort:1802, + sforwardPort:80, + sforwardPort1:10000, + sforwardPort2:60000, }, rules: { name: [{ required: true, message: "必填", trigger: "blur" }], @@ -148,9 +207,10 @@ export default { \ No newline at end of file diff --git a/linker/client/config/Config.cs b/linker/client/config/Config.cs index 4606251d..5b182f2f 100644 --- a/linker/client/config/Config.cs +++ b/linker/client/config/Config.cs @@ -67,7 +67,7 @@ namespace linker.config } } #if DEBUG - private string groupid = "snltty"; + private string groupid = string.Empty;//"snltty"; #else private string groupid = string.Empty; #endif diff --git a/linker/plugins/forward/proxy/ForwardProxy.cs b/linker/plugins/forward/proxy/ForwardProxy.cs index 0411255f..1af2e349 100644 --- a/linker/plugins/forward/proxy/ForwardProxy.cs +++ b/linker/plugins/forward/proxy/ForwardProxy.cs @@ -113,6 +113,8 @@ namespace linker.plugins.forward.proxy if (connection == null) { if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) LoggerHelper.Instance.Debug($"forward relay to {machineId}"); + //转入后台打洞 + tunnelTransfer.StartBackground(machineId, "forward"); //尝试中继 connection = await relayTransfer.ConnectAsync(config.Data.Client.Id, machineId, "forward").ConfigureAwait(false); if (connection != null) diff --git a/linker/plugins/tuntap/proxy/TuntapProxy.cs b/linker/plugins/tuntap/proxy/TuntapProxy.cs index ae3bd54c..f13195e7 100644 --- a/linker/plugins/tuntap/proxy/TuntapProxy.cs +++ b/linker/plugins/tuntap/proxy/TuntapProxy.cs @@ -262,6 +262,7 @@ namespace linker.plugins.tuntap.proxy { if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) LoggerHelper.Instance.Debug($"tuntap relay to {machineId}"); + tunnelTransfer.StartBackground(machineId, "tuntap"); connection = await relayTransfer.ConnectAsync(config.Data.Client.Id, machineId, "tuntap").ConfigureAwait(false); if (connection != null) { diff --git a/linker/plugins/updater/UpdaterStartup.cs b/linker/plugins/updater/UpdaterStartup.cs new file mode 100644 index 00000000..83638ebe --- /dev/null +++ b/linker/plugins/updater/UpdaterStartup.cs @@ -0,0 +1,43 @@ +using linker.config; +using linker.libs; +using linker.startup; +using Microsoft.Extensions.DependencyInjection; +using System.Diagnostics; +using System.Reflection; + +namespace linker.plugins.updater +{ + public sealed class UpdaterStartup : IStartup + { + public string Name => "updater"; + + public bool Required => false; + + public StartupLevel Level => StartupLevel.Normal; + + public string[] Dependent => Array.Empty(); + + public StartupLoadType LoadType => StartupLoadType.Normal; + + public void AddClient(ServiceCollection serviceCollection, ConfigWrap config, Assembly[] assemblies) + { + } + + public void AddServer(ServiceCollection serviceCollection, ConfigWrap config, Assembly[] assemblies) + { + } + + public void UseClient(ServiceProvider serviceProvider, ConfigWrap config, Assembly[] assemblies) + { + foreach (var item in Process.GetProcessesByName("linker.updater")) + { + item.Kill(); + } + //CommandHelper.Execute(); + } + + public void UseServer(ServiceProvider serviceProvider, ConfigWrap config, Assembly[] assemblies) + { + } + } +} diff --git a/linker/plugins/updater/config/config.cs b/linker/plugins/updater/config/config.cs new file mode 100644 index 00000000..80533b3a --- /dev/null +++ b/linker/plugins/updater/config/config.cs @@ -0,0 +1,52 @@ +using linker.plugins.updater.config; + +namespace linker.plugins.updater.config +{ + 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; + } + } + } + } +} + + +namespace linker.client.config +{ + public sealed partial class ConfigClientInfo + { + public UpdaterConfigInfo Updater { get; set; } = new UpdaterConfigInfo(); + } +} \ No newline at end of file diff --git a/publish.bat b/publish.bat index 61b0fcaf..e98b620e 100644 --- a/publish.bat +++ b/publish.bat @@ -27,7 +27,7 @@ for %%r in (win-x64,win-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=full -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 + 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 echo F|xcopy "public\\extends\\%%r\\linker-%%r\\*" "public\\publish\\%%r\\linker-%%r\\*" /s /f /h /y