From 4fe8b03daa091a08533be78ebcc40a03407381e3 Mon Sep 17 00:00:00 2001 From: autumn Date: Mon, 16 Aug 2021 18:25:43 +0800 Subject: [PATCH] review --- ProxySU.sln | 4 +- ProxySuper.Core/Models/Hosts/LocalProxy.cs | 6 +- .../Models/Hosts/LocalProxyType.cs | 19 - ProxySuper.Core/Models/ProjectProgress.cs | 83 ++ ProxySuper.Core/ProxySuper.Core.csproj | 5 +- ProxySuper.Core/Services/ServiceBase.cs | 740 ++++++++++++++++++ ProxySuper.Core/Services/XrayService.cs | 127 +++ .../ViewModels/EnableRootViewModel.cs | 4 +- ProxySuper.Core/ViewModels/HomeViewModel.cs | 2 +- .../ViewModels/XrayInstallViewModel.cs | 68 ++ ProxySuper.WPF/Controls/HostControl.xaml | 7 +- ProxySuper.WPF/Controls/ProgressControl.xaml | 53 ++ .../Controls/ProgressControl.xaml.cs | 37 + ProxySuper.WPF/ProxySuper.WPF.csproj | 19 +- .../Views/BrookInstallerView.xaml.cs | 4 +- .../Views/NaiveProxyInstallerView.xaml.cs | 4 +- .../Views/TrojanGoInstallerView.xaml.cs | 4 +- ProxySuper.WPF/Views/XrayInstallView.xaml | 38 + ProxySuper.WPF/Views/XrayInstallView.xaml.cs | 30 + .../Views/XrayInstallerView.xaml.cs | 4 +- 20 files changed, 1219 insertions(+), 39 deletions(-) delete mode 100644 ProxySuper.Core/Models/Hosts/LocalProxyType.cs create mode 100644 ProxySuper.Core/Models/ProjectProgress.cs create mode 100644 ProxySuper.Core/Services/ServiceBase.cs create mode 100644 ProxySuper.Core/Services/XrayService.cs create mode 100644 ProxySuper.Core/ViewModels/XrayInstallViewModel.cs create mode 100644 ProxySuper.WPF/Controls/ProgressControl.xaml create mode 100644 ProxySuper.WPF/Controls/ProgressControl.xaml.cs create mode 100644 ProxySuper.WPF/Views/XrayInstallView.xaml create mode 100644 ProxySuper.WPF/Views/XrayInstallView.xaml.cs diff --git a/ProxySU.sln b/ProxySU.sln index 78ce6b6..a38b700 100644 --- a/ProxySU.sln +++ b/ProxySU.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31005.135 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31606.5 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libs", "Libs", "{CE908112-DB46-4B91-8236-9139A19D66E9}" EndProject diff --git a/ProxySuper.Core/Models/Hosts/LocalProxy.cs b/ProxySuper.Core/Models/Hosts/LocalProxy.cs index 4078f29..c382bbe 100644 --- a/ProxySuper.Core/Models/Hosts/LocalProxy.cs +++ b/ProxySuper.Core/Models/Hosts/LocalProxy.cs @@ -1,4 +1,6 @@ -namespace ProxySuper.Core.Models.Hosts +using Renci.SshNet; + +namespace ProxySuper.Core.Models.Hosts { public class LocalProxy { @@ -6,7 +8,7 @@ public int Port { get; set; } = 1080; - public LocalProxyType Type { get; set; } + public ProxyTypes Type { get; set; } public string UserName { get; set; } diff --git a/ProxySuper.Core/Models/Hosts/LocalProxyType.cs b/ProxySuper.Core/Models/Hosts/LocalProxyType.cs deleted file mode 100644 index e055d81..0000000 --- a/ProxySuper.Core/Models/Hosts/LocalProxyType.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace ProxySuper.Core.Models.Hosts -{ - public enum LocalProxyType - { - None = 0, - // - // 摘要: - // A SOCKS4 proxy server. - Socks4 = 1, - // - // 摘要: - // A SOCKS5 proxy server. - Socks5 = 2, - // - // 摘要: - // A HTTP proxy server. - Http = 3 - } -} diff --git a/ProxySuper.Core/Models/ProjectProgress.cs b/ProxySuper.Core/Models/ProjectProgress.cs new file mode 100644 index 0000000..dad33a3 --- /dev/null +++ b/ProxySuper.Core/Models/ProjectProgress.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProxySuper.Core.Models +{ + public class ProjectProgress + { + private string _step; + + private string _desc; + + private int _percentage; + + private string _logs; + + public ProjectProgress() + { + _step = "步骤"; + + _desc = "步骤描述"; + + _percentage = 0; + + _logs = string.Empty; + + StepUpdate = () => { }; + } + + + public Action StepUpdate { get; set; } + + public Action LogsUpdate { get; set; } + + public string Desc + { + get { return _desc; } + set + { + _desc = value; + StepUpdate(); + + _logs += _desc + "\n"; + LogsUpdate(); + } + } + + public string Step + { + get { return _step; } + set + { + _step = value; + StepUpdate(); + + _logs += Step + "\n"; + LogsUpdate(); + } + } + + public int Percentage + { + get { return _percentage; } + set + { + _percentage = value; + StepUpdate(); + } + } + + public string Logs + { + get { return _logs; } + set + { + _logs = value; + LogsUpdate(); + } + } + } +} \ No newline at end of file diff --git a/ProxySuper.Core/ProxySuper.Core.csproj b/ProxySuper.Core/ProxySuper.Core.csproj index 5de987e..02c6703 100644 --- a/ProxySuper.Core/ProxySuper.Core.csproj +++ b/ProxySuper.Core/ProxySuper.Core.csproj @@ -69,8 +69,8 @@ - + @@ -90,11 +90,13 @@ + + @@ -110,6 +112,7 @@ + diff --git a/ProxySuper.Core/Services/ServiceBase.cs b/ProxySuper.Core/Services/ServiceBase.cs new file mode 100644 index 0000000..3d9aad7 --- /dev/null +++ b/ProxySuper.Core/Services/ServiceBase.cs @@ -0,0 +1,740 @@ +using ProxySuper.Core.Helpers; +using ProxySuper.Core.Models; +using ProxySuper.Core.Models.Hosts; +using ProxySuper.Core.Models.Projects; +using Renci.SshNet; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProxySuper.Core.Services +{ + public abstract class ServiceBase where TSettings : IProjectSettings + { + private Host _host; + + + private SshClient _sshClient; + + private ProjectProgress _progress; + + public ServiceBase(Host host, TSettings settings) + { + _host = host; + + Settings = settings; + + _sshClient = new SshClient(CreateConnectionInfo()); + + _progress = new ProjectProgress(); + + ArchType = ArchType.x86; + + CmdType = CmdType.None; + + IPv4 = string.Empty; + + IPv6 = string.Empty; + + IsOnlyIPv6 = false; + } + + public string RunCmd(string command) + { + AppendCommand(command); + + string result; + if (_sshClient.IsConnected) + { + result = _sshClient.CreateCommand(command).Execute(); + } + else + { + result = "连接已断开"; + } + + AppendCommand(result); + + return result; + } + + public ProjectProgress Progress => _progress; + + public TSettings Settings { get; set; } + + public ArchType ArchType { get; set; } + + public CmdType CmdType { get; set; } + + public string IPv4 { get; set; } + + public string IPv6 { get; set; } + + public bool IsOnlyIPv6 { get; set; } + + + #region 公用方法 + public void Connect() + { + Task.Run(() => + { + if (_sshClient.IsConnected == false) + { + Progress.Desc = ("正在与服务器建立连接"); + try + { + _sshClient.Connect(); + Progress.Desc = ("建立连接成功"); + } + catch (Exception e) + { + Progress.Desc = ("连接失败," + e.Message); + } + } + }); + } + + public void Disconnect() + { + Task.Run(() => + { + _sshClient.Disconnect(); + }); + } + + protected void WriteToFile(string text, string path) + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(text))) + { + using (var sftp = new SftpClient(_sshClient.ConnectionInfo)) + { + try + { + sftp.Connect(); + sftp.UploadFile(stream, path, true); + } + catch (Exception ex) + { + throw ex; + } + finally + { + sftp.Disconnect(); + } + } + } + } + + protected bool FileExists(string path) + { + var cmdStr = $"if [[ -f {path} ]];then echo '1';else echo '0'; fi"; + var cmd = RunCmd(cmdStr); + return cmd.Trim() == "1"; + } + + protected void SyncTimeDiff() + { + RunCmd("rm -f /etc/localtime"); + RunCmd("ln -s /usr/share/zoneinfo/UTC /etc/localtime"); + + var result = RunCmd("date +%s"); + var vpsSeconds = Convert.ToInt64(result); + var localSeconds = (int)(DateTime.Now.ToUniversalTime() - DateTime.Parse("1970-01-01")).TotalSeconds; + + if (Math.Abs(vpsSeconds - localSeconds) >= 90) + { + // 同步本地时间 + var netUtcTime = DateTimeUtils.GetUTCTime(); + DateTimeUtils.SetDate(netUtcTime.ToLocalTime()); + + // 同步VPS时间 + var utcTS = DateTimeUtils.GetUTCTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0); + long timeStampVPS = Convert.ToInt64(utcTS.TotalSeconds); + RunCmd($"date --set=\"$(date \"+%Y-%m-%d %H:%M:%S\" -d @{timeStampVPS.ToString()})\""); + } + } + + protected void ValidateDomain() + { + var domainIP = RunCmd($"ping \"{Settings.Domain}\" -c 1" + @" | sed '1{s/[^(]*(//;s/).*//;q}'") + .Trim('\r', '\n'); + + if (IsOnlyIPv6) + { + Progress.Desc = ($"本机IP({IPv6})"); + if (IPv6 != domainIP) + { + throw new Exception("域名解析地址与服务器IP不匹配!"); + } + } + else + { + Progress.Desc = ($"本机IP({IPv4})"); + Progress.Desc = ($"域名IP({domainIP})"); + if (IPv4 != domainIP) + { + throw new Exception("域名解析地址与服务器IP不匹配!"); + } + } + } + + protected void EnableBBR() + { + Progress.Desc = ("检查系统是否满足启动BBR条件"); + var osVersion = RunCmd("uname -r"); + var canInstallBBR = CheckKernelVersionBBR(osVersion.Split('-')[0]); + + var bbrInfo = RunCmd("sysctl net.ipv4.tcp_congestion_control | grep bbr"); + var installed = bbrInfo.Contains("bbr"); + if (canInstallBBR && !installed) + { + RunCmd(@"bash -c 'echo ""net.core.default_qdisc=fq"" >> /etc/sysctl.conf'"); + RunCmd(@"bash -c 'echo ""net.ipv4.tcp_congestion_control=bbr"" >> /etc/sysctl.conf'"); + RunCmd(@"sysctl -p"); + + if (IsOnlyIPv6) + { + RemoveNat64(); + } + Progress.Desc = ("启动BBR成功"); + } + + if (!canInstallBBR) + { + Progress.Desc = ("系统不满足启用BBR条件,启动失败。"); + } + + } + + /// + /// 安装证书 + /// + /// + /// + protected void InstallCert(string dirPath, string certName, string keyName) + { + string certPath = dirPath + "/" + certName; + string keyPath = dirPath + "/" + keyName; + + Progress.Desc = ("安装Acme软件"); + #region 安装Acme + // 安装依赖 + RunCmd(GetInstallCmd("socat")); + + // 解决搬瓦工CentOS缺少问题 + RunCmd(GetInstallCmd("automake autoconf libtool")); + + // 安装Acme + var result = RunCmd($"curl https://get.acme.sh yes | sh"); + if (!result.Contains("nstall success")) + { + throw new Exception("安装 Acme 失败,请联系开发者!"); + } + + RunCmd("alias acme.sh=~/.acme.sh/acme.sh"); + + #endregion + + + #region 申请证书 + Progress.Desc = ("正在申请证书"); + // 申请证书 + var cmd = $"/root/.acme.sh/acme.sh --force --debug --issue --standalone -d {Settings.Domain} {(IsOnlyIPv6 ? "--listen-v6" : "")} --pre-hook \"systemctl stop caddy\" --post-hook \"systemctl start caddy\" --server letsencrypt"; + result = RunCmd(cmd); + + + if (result.Contains("success")) + { + Progress.Desc = ("申请证书成功"); + } + else + { + Progress.Desc = ("申请证书失败,如果申请次数过多请更换二级域名,或联系开发者!"); + throw new Exception("申请证书失败,如果申请次数过多请更换二级域名,或联系开发者!"); + } + #endregion + + // 安装证书 + Progress.Desc = ("安装Xray证书"); + RunCmd($"mkdir -p {dirPath}"); + RunCmd($"/root/.acme.sh/acme.sh --installcert -d {Settings.Domain} --certpath {certPath} --keypath {keyPath} --capath {certPath}"); + + result = RunCmd($@"if [ ! -f ""{keyPath}"" ]; then echo ""0""; else echo ""1""; fi | head -n 1"); + + if (result.Contains("1")) + { + Progress.Desc = ("安装证书成功"); + } + else + { + Progress.Desc = ("安装证书失败,请联系开发者!"); + throw new Exception("安装证书失败,请联系开发者!"); + } + + RunCmd($"chmod 755 {dirPath}"); + } + + + public bool IsRootUser() + { + // 禁止一些可能产生的干扰信息 + RunCmd(@"sed -i 's/echo/#echo/g' ~/.bashrc"); + RunCmd(@"sed -i 's/echo/#echo/g' ~/.profile"); + + var result = RunCmd("id -u"); + return result.Equals("0\n"); + } + + public void EnsureSystemEnv() + { + // cpu架构 + Progress.Desc = ("检测CPU架构"); + EnsureCPU(); + + // 安装命令类型 + Progress.Desc = ("检测系统安装命令"); + EnsureCmdType(); + + // systemctl + Progress.Desc = ("检测Systemctl"); + EnsureSystemctl(); + + // SELinux + Progress.Desc = ("检测SELinux"); + ConfigSELinux(); + } + + public void InstallSystemTools() + { + Progress.Desc = ("安装sudo工具"); + InstallSoftware("sudo"); + + Progress.Desc = ("安装curl工具"); + InstallSoftware("curl"); + + Progress.Desc = ("安装wget工具"); + InstallSoftware("wget"); + + Progress.Desc = ("安装ping工具"); + InstallSoftware("ping"); + + Progress.Desc = ("安装unzip工具"); + InstallSoftware("unzip"); + + Progress.Desc = ("安装cron工具"); + InstallSoftware("cron"); + + Progress.Desc = ("安装lsof工具"); + InstallSoftware("lsof"); + + Progress.Desc = ("安装systemd工具"); + InstallSoftware("systemd"); + } + + public void ConfigFirewalld() + { + Progress.Desc = ("释放被占用的端口"); + Settings.FreePorts.ForEach(port => SetPortFree(port)); + + Progress.Desc = ("开放需要的端口"); + OpenPort(Settings.FreePorts.ToArray()); + } + + public void EnsureNetwork() + { + string cmd; + + Progress.Desc = ("检测IPv4"); + cmd = RunCmd(@"curl -s https://api.ip.sb/ip --ipv4 --max-time 8"); + IPv4 = cmd.TrimEnd('\r', '\n'); + + Progress.Desc = ($"IPv4地址为{IPv4}"); + if (!string.IsNullOrEmpty(IPv4)) + { + IsOnlyIPv6 = false; + } + else + { + Progress.Desc = ("检测IPv6"); + cmd = RunCmd(@"curl -s https://api.ip.sb/ip --ipv6 --max-time 8"); + IPv6 = cmd.TrimEnd('\r', '\n'); + Progress.Desc = ($"IPv6地址为{IPv6}"); + + IsOnlyIPv6 = true; + SetNat64(); + } + + if (string.IsNullOrEmpty(IPv4) && string.IsNullOrEmpty(IPv6)) + { + throw new Exception("未检测到服务器公网IP,请检查网络或重试。"); + } + } + + public void InstallCaddy() + { + RunCmd("rm -rf caddy.tar.gz"); + RunCmd("rm -rf /etc/caddy"); + RunCmd("rm -rf /usr/share/caddy"); + + var url = "https://github.com/caddyserver/caddy/releases/download/v2.4.3/caddy_2.4.3_linux_amd64.tar.gz"; + if (ArchType == ArchType.arm) + { + url = "https://github.com/caddyserver/caddy/releases/download/v2.4.3/caddy_2.4.3_linux_armv7.tar.gz"; + } + + RunCmd($"wget -O caddy.tar.gz {url}"); + RunCmd("mkdir /etc/caddy"); + RunCmd("tar -zxvf caddy.tar.gz -C /etc/caddy"); + RunCmd("cp -rf /etc/caddy/caddy /usr/bin"); + WriteToFile(Caddy.DefaultCaddyFile, "/etc/caddy/Caddyfile"); + WriteToFile(Caddy.Service, "/etc/systemd/system/caddy.service"); + RunCmd("systemctl daemon-reload"); + RunCmd("systemctl enable caddy"); + + RunCmd("mkdir /usr/share/caddy"); + RunCmd("chmod 775 /usr/share/caddy"); + + if (!FileExists("/usr/bin/caddy")) + { + throw new Exception("Caddy服务器安装失败,请联系开发者!"); + } + } + #endregion + + + #region 检测System环境 + private void EnsureCPU() + { + var result = RunCmd("uname -m"); + if (result.Contains("x86")) + { + ArchType = ArchType.x86; + } + else if (result.Contains("arm") || result.Contains("arch")) + { + ArchType = ArchType.arm; + } + } + + private void EnsureCmdType() + { + var result = string.Empty; + + if (CmdType == CmdType.None) + { + result = RunCmd("command -v apt"); + if (!string.IsNullOrEmpty(result)) + { + CmdType = CmdType.Apt; + } + } + + if (CmdType == CmdType.None) + { + result = RunCmd("command -v dnf"); + if (!string.IsNullOrEmpty(result)) + { + CmdType = CmdType.Dnf; + } + } + + if (CmdType == CmdType.None) + { + result = RunCmd("command -v yum"); + if (!string.IsNullOrEmpty(result)) + { + CmdType = CmdType.Yum; + } + } + + if (CmdType == CmdType.None) + { + throw new Exception("未检测到正确的系统安装命令,请尝试使用ProxySU推荐的系统版本安装!"); + } + } + + private void EnsureSystemctl() + { + var result = RunCmd("command -v systemctl"); + if (string.IsNullOrEmpty(result)) + { + throw new Exception("系统缺少 systemctl 组件,请尝试使用ProxySU推荐的系统版本安装!"); + } + } + + private void ConfigSELinux() + { + // SELinux + var result = RunCmd("command -v getenforce"); + var isSELinux = !string.IsNullOrEmpty(result); + + // 判断是否启用了SELinux,如果启用了,并且工作在Enforcing模式下,则改为Permissive模式 + if (isSELinux) + { + result = RunCmd("getenforce"); + + // 检测到系统启用SELinux,且工作在严格模式下,需改为宽松模式 + if (result.Contains("Enforcing")) + { + RunCmd("setenforce 0"); + RunCmd(@"sed -i 's/SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config"); + } + } + } + #endregion + + + #region 私有方法 + + protected void SetNat64() + { + var dns64List = FilterFastestIP(); + if (dns64List.Count == 0) + { + throw new Exception("未找到有效的Nat64网关"); + } + + var exists = FileExists("/etc/resolv.conf.proxysu"); + if (!exists) + { + var cmdStr = @"mv /etc/resolv.conf /etc/resolv.conf.proxysu"; + RunCmd(cmdStr); + } + + foreach (var gateip in dns64List) + { + RunCmd($"echo \"nameserver {gateip}\" > /etc/resolv.conf"); + } + } + + protected void RemoveNat64() + { + RunCmd("rm /etc/resolv.conf"); + RunCmd("mv /etc/resolv.conf.proxysu /etc/resolv.conf"); + } + + private List FilterFastestIP() + { + string[] gateNat64 = { + "2a01:4f9:c010:3f02::1", + "2001:67c:2b0::4", + "2001:67c:2b0::6", + "2a09:11c0:f1:bbf0::70", + "2a01:4f8:c2c:123f::1", + "2001:67c:27e4:15::6411", + "2001:67c:27e4::64", + "2001:67c:27e4:15::64", + "2001:67c:27e4::60", + "2a00:1098:2b::1", + "2a03:7900:2:0:31:3:104:161", + "2a00:1098:2c::1", + "2a09:11c0:100::53", + }; + + Dictionary dns64List = new Dictionary(); + foreach (var gateip in gateNat64) + { + var cmdStr = $"ping6 -c4 {gateip} | grep avg | awk '{{print $4}}'|cut -d/ -f2"; + var cmd = RunCmd(cmdStr); + if (!string.IsNullOrEmpty(cmd)) + { + if (float.TryParse(cmd, out float delay)) + { + dns64List.Add(gateip, delay); + } + } + } + + return dns64List.Keys.ToList(); + } + + private bool CheckKernelVersionBBR(string kernelVer) + { + string[] linuxKernelCompared = kernelVer.Split('.'); + if (int.Parse(linuxKernelCompared[0]) > 4) + { + return true; + } + else if (int.Parse(linuxKernelCompared[0]) < 4) + { + return false; + } + else if (int.Parse(linuxKernelCompared[0]) == 4) + { + if (int.Parse(linuxKernelCompared[1]) >= 9) + { + return true; + } + else if (int.Parse(linuxKernelCompared[1]) < 9) + { + return false; + } + + } + return false; + + } + + private void SetPortFree(int port) + { + string result = RunCmd($"lsof -n -P -i :{port} | grep LISTEN"); + + if (!string.IsNullOrEmpty(result)) + { + string[] process = result.Split(' '); + RunCmd($"systemctl stop {process[0]}"); + RunCmd($"systemctl disable {process[0]}"); + RunCmd($"pkill {process[0]}"); + } + } + + private void OpenPort(params int[] portList) + { + string cmd; + + cmd = RunCmd("command -v firewall-cmd"); + if (!string.IsNullOrEmpty(cmd)) + { + //有很奇怪的vps主机,在firewalld未运行时,端口是关闭的,无法访问。所以要先启动firewalld + //用于保证acme.sh申请证书成功 + cmd = RunCmd("firewall-cmd --state"); + if (cmd.Trim() != "running") + { + RunCmd("systemctl restart firewalld"); + } + + foreach (var port in portList) + { + RunCmd($"firewall-cmd --add-port={port}/tcp --permanent"); + RunCmd($"firewall-cmd --add-port={port}/udp --permanent"); + } + + RunCmd("yes | firewall-cmd --reload"); + } + else + { + cmd = RunCmd("command -v ufw"); + if (string.IsNullOrEmpty(cmd)) + { + RunCmd(GetInstallCmd("ufw")); + RunCmd("echo y | ufw enable"); + } + + foreach (var port in portList) + { + RunCmd($"ufw allow {port}/tcp"); + RunCmd($"ufw allow {port}/udp"); + } + RunCmd("yes | ufw reload"); + } + } + + private void ClosePort(params int[] portList) + { + string cmd; + + cmd = RunCmd("command -v firewall-cmd"); + if (!string.IsNullOrEmpty(cmd)) + { + //有很奇怪的vps主机,在firewalld未运行时,端口是关闭的,无法访问。所以要先启动firewalld + //用于保证acme.sh申请证书成功 + cmd = RunCmd("firewall-cmd --state"); + if (cmd.Trim() != "running") + { + RunCmd("systemctl restart firewalld"); + } + + foreach (var port in portList) + { + RunCmd($"firewall-cmd --remove-port={port}/tcp --permanent"); + RunCmd($"firewall-cmd --remove-port={port}/udp --permanent"); + } + RunCmd("yes | firewall-cmd --reload"); + } + else + { + cmd = RunCmd("command -v ufw"); + if (!string.IsNullOrEmpty(cmd)) + { + foreach (var port in portList) + { + RunCmd($"ufw delete allow {port}/tcp"); + RunCmd($"ufw delete allow {port}/udp"); + } + RunCmd("yes | ufw reload"); + } + } + } + + private void InstallSoftware(string software) + { + var result = RunCmd($"command -v {software}"); + if (string.IsNullOrEmpty(result)) + { + RunCmd(GetInstallCmd(software)); + } + } + + private string GetInstallCmd(string soft) + { + if (CmdType == CmdType.Apt) + { + return $"apt install -y {soft}"; + } + else if (CmdType == CmdType.Yum) + { + return $"yum install -y {soft}"; + } + else + { + return $"dnf install -y {soft}"; + } + } + + private void AppendCommand(string command) + { + if (!command.EndsWith("\n")) + { + command += "\n"; + } + Progress.Logs += command; + } + + private ConnectionInfo CreateConnectionInfo() + { + var authMethods = new List(); + + if (!string.IsNullOrEmpty(_host.Password)) + { + authMethods.Add(new PasswordAuthenticationMethod(_host.UserName, _host.Password)); + } + + if (_host.SecretType == LoginSecretType.PrivateKey) + { + authMethods.Add(new PrivateKeyAuthenticationMethod(_host.UserName, new PrivateKeyFile(_host.PrivateKeyPath))); + } + + if (_host.Proxy.Type == ProxyTypes.None) + { + return new ConnectionInfo( + host: _host.Address, + username: _host.Password, + authenticationMethods: authMethods.ToArray()); + } + + return new ConnectionInfo( + host: _host.Address, + port: _host.Port, + username: _host.UserName, + proxyType: _host.Proxy.Type, + proxyHost: _host.Proxy.Address, + proxyPort: _host.Proxy.Port, + proxyUsername: _host.Proxy.UserName, proxyPassword: _host.Proxy.Password, + authenticationMethods: authMethods.ToArray()); + } + #endregion + } +} diff --git a/ProxySuper.Core/Services/XrayService.cs b/ProxySuper.Core/Services/XrayService.cs new file mode 100644 index 0000000..68df0b5 --- /dev/null +++ b/ProxySuper.Core/Services/XrayService.cs @@ -0,0 +1,127 @@ +using ProxySuper.Core.Helpers; +using ProxySuper.Core.Models.Hosts; +using ProxySuper.Core.Models.Projects; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace ProxySuper.Core.Services +{ + public class XrayService : ServiceBase + { + + public XrayService(Host host, XraySettings settings) : base(host, settings) + { + } + + public void Install() + { + Task.Run(() => + { + int index = 1; + if (!IsRootUser()) + { + MessageBox.Show("ProxySU需要使用Root用户进行安装!"); + return; + } + + Progress.Step = $"{index++}. 检测系统环境"; + EnsureSystemEnv(); + Progress.Percentage = 5; + + Progress.Step = $"{index++}. 安装必要的系统工具"; + InstallSystemTools(); + Progress.Percentage = 15; + + Progress.Step = $"{index++}. 配置防火墙"; + ConfigFirewalld(); + Progress.Percentage = 20; + + Progress.Step = $"{index++}. 检测网络环境"; + EnsureNetwork(); + if (Settings.IsIPAddress) + { + Progress.Desc = ("检查域名是否解析正确"); + ValidateDomain(); + } + Progress.Percentage = 25; + + Progress.Step = $"{index}. 同步系统和本地时间"; + SyncTimeDiff(); + Progress.Percentage = 30; + + Progress.Step = $"{index++}. 安装Caddy服务器"; + InstallCaddy(); + Progress.Percentage = 50; + + Progress.Step = $"{index++}. 安装Xray-Core"; + InstallXray(); + Progress.Percentage = 80; + + Progress.Step = $"{index++}. 上传Web服务器配置"; + UploadCaddyFile(); + Progress.Percentage = 90; + + Progress.Step = $"{index++}. 启动BBR"; + EnableBBR(); + Progress.Percentage = 100; + + Progress.Desc = ("!!!安装Xray完成!!!"); + }); + } + + public void InstallXray() + { + Progress.Desc = ("开始安装Xray-Core"); + RunCmd("bash -c \"$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)\" @ install"); + + if (!FileExists("/usr/local/bin/xray")) + { + Progress.Desc = ("Xray-Core安装失败,请联系开发者"); + throw new Exception("Xray-Core安装失败,请联系开发者"); + } + + Progress.Desc = ("设置Xray-core权限"); + RunCmd($"sed -i 's/User=nobody/User=root/g' /etc/systemd/system/xray.service"); + RunCmd($"sed -i 's/CapabilityBoundingSet=/#CapabilityBoundingSet=/g' /etc/systemd/system/xray.service"); + RunCmd($"sed -i 's/AmbientCapabilities=/#AmbientCapabilities=/g' /etc/systemd/system/xray.service"); + RunCmd($"systemctl daemon-reload"); + + if (FileExists("/usr/local/etc/xray/config.json")) + { + RunCmd(@"mv /usr/local/etc/xray/config.json /usr/local/etc/xray/config.json.1"); + } + Progress.Percentage = 60; + + if (!Settings.IsIPAddress) + { + Progress.Desc = ("安装TLS证书"); + InstallCert( + dirPath: "/usr/local/etc/xray/ssl", + certName: "xray_ssl.crt", + keyName: "xray_ssl.key"); + Progress.Percentage = 75; + } + + Progress.Desc = ("生成Xray服务器配置文件"); + var configJson = XrayConfigBuilder.BuildXrayConfig(Settings); + WriteToFile(configJson, "/usr/local/etc/xray/config.json"); + RunCmd("systemctl restart xray"); + } + + private void UploadCaddyFile(bool useCustomWeb = false) + { + var configJson = XrayConfigBuilder.BuildCaddyConfig(Settings, useCustomWeb); + + if (FileExists("/etc/caddy/Caddyfile")) + { + RunCmd("mv /etc/caddy/Caddyfile /etc/caddy/Caddyfile.back"); + } + WriteToFile(configJson, "/etc/caddy/Caddyfile"); + RunCmd("systemctl restart caddy"); + } + } +} diff --git a/ProxySuper.Core/ViewModels/EnableRootViewModel.cs b/ProxySuper.Core/ViewModels/EnableRootViewModel.cs index 96b45d1..3ae228c 100644 --- a/ProxySuper.Core/ViewModels/EnableRootViewModel.cs +++ b/ProxySuper.Core/ViewModels/EnableRootViewModel.cs @@ -145,7 +145,7 @@ namespace ProxySuper.Core.ViewModels auth = new PrivateKeyAuthenticationMethod(host.UserName, new PrivateKeyFile(host.PrivateKeyPath)); } - if (host.Proxy.Type == LocalProxyType.None) + if (host.Proxy.Type == ProxyTypes.None) { return new ConnectionInfo(host.Address, host.Port, host.UserName, auth); } @@ -155,7 +155,7 @@ namespace ProxySuper.Core.ViewModels host: host.Address, port: host.Port, username: host.UserName, - proxyType: (ProxyTypes)(int)host.Proxy.Type, + proxyType: host.Proxy.Type, proxyHost: host.Proxy.Address, proxyPort: host.Proxy.Port, proxyUsername: host.Proxy.UserName, diff --git a/ProxySuper.Core/ViewModels/HomeViewModel.cs b/ProxySuper.Core/ViewModels/HomeViewModel.cs index 8b749de..c20cd2a 100644 --- a/ProxySuper.Core/ViewModels/HomeViewModel.cs +++ b/ProxySuper.Core/ViewModels/HomeViewModel.cs @@ -221,7 +221,7 @@ namespace ProxySuper.Core.ViewModels if (record.Type == ProjectType.Xray) { - await _navigationService.Navigate(record); + await _navigationService.Navigate(record); } if (record.Type == ProjectType.TrojanGo) { diff --git a/ProxySuper.Core/ViewModels/XrayInstallViewModel.cs b/ProxySuper.Core/ViewModels/XrayInstallViewModel.cs new file mode 100644 index 0000000..69ea300 --- /dev/null +++ b/ProxySuper.Core/ViewModels/XrayInstallViewModel.cs @@ -0,0 +1,68 @@ +using MvvmCross.Commands; +using MvvmCross.ViewModels; +using ProxySuper.Core.Models; +using ProxySuper.Core.Models.Hosts; +using ProxySuper.Core.Models.Projects; +using ProxySuper.Core.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProxySuper.Core.ViewModels +{ + public class XrayInstallViewModel : MvxViewModel + { + Host _host; + + XraySettings _settings; + + XrayService _xrayService; + + MvxInteraction _refreshLogInteraction = new MvxInteraction(); + + public override void ViewDestroy(bool viewFinishing = true) + { + _xrayService.Disconnect(); + base.ViewDestroy(viewFinishing); + } + + public override void Prepare(Record parameter) + { + this._host = parameter.Host; + this._settings = parameter.XraySettings; + } + + public override Task Initialize() + { + _xrayService = new XrayService(_host, _settings); + _xrayService.Progress.StepUpdate = () => RaisePropertyChanged("Progress"); + _xrayService.Progress.LogsUpdate = () => + { + RaisePropertyChanged("Logs"); + _refreshLogInteraction.Raise(); + }; + _xrayService.Connect(); + + return base.Initialize(); + } + + public ProjectProgress Progress + { + get => _xrayService.Progress; + } + + public string Logs + { + get => _xrayService.Progress.Logs; + } + + public IMvxInteraction LogsInteraction + { + get => _refreshLogInteraction; + } + + public IMvxCommand InstallCommand => new MvxCommand(_xrayService.Install); + } +} diff --git a/ProxySuper.WPF/Controls/HostControl.xaml b/ProxySuper.WPF/Controls/HostControl.xaml index 7ec9b0c..0825885 100644 --- a/ProxySuper.WPF/Controls/HostControl.xaml +++ b/ProxySuper.WPF/Controls/HostControl.xaml @@ -6,6 +6,7 @@ xmlns:local="clr-namespace:ProxySuper.WPF.Controls" xmlns:convert="clr-namespace:ProxySuper.Core.Converters;assembly=ProxySuper.Core" xmlns:host="clr-namespace:ProxySuper.Core.Models.Hosts;assembly=ProxySuper.Core" + xmlns:ssh="clr-namespace:Renci.SshNet;assembly=Renci.SshNet" mc:Ignorable="d"> @@ -129,21 +130,21 @@ IsChecked="{ Binding Host.Proxy.Type, Converter={StaticResource ProxyTypeConverter}, - ConverterParameter={x:Static host:LocalProxyType.None} + ConverterParameter={x:Static ssh:ProxyTypes.None} }"/> diff --git a/ProxySuper.WPF/Controls/ProgressControl.xaml b/ProxySuper.WPF/Controls/ProgressControl.xaml new file mode 100644 index 0000000..a00c760 --- /dev/null +++ b/ProxySuper.WPF/Controls/ProgressControl.xaml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + diff --git a/ProxySuper.WPF/Controls/ProgressControl.xaml.cs b/ProxySuper.WPF/Controls/ProgressControl.xaml.cs new file mode 100644 index 0000000..f6fe564 --- /dev/null +++ b/ProxySuper.WPF/Controls/ProgressControl.xaml.cs @@ -0,0 +1,37 @@ +using MvvmCross.ViewModels; +using ProxySuper.Core.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace ProxySuper.WPF.Controls +{ + /// + /// ProgressControl.xaml 的交互逻辑 + /// + public partial class ProgressControl : UserControl + { + public ProgressControl() + { + InitializeComponent(); + + LogsTextBox.TextChanged += LogsTextBox_TextChanged; + } + + private void LogsTextBox_TextChanged(object sender, TextChangedEventArgs e) + { + LogsTextBox.ScrollToEnd(); + } + } +} diff --git a/ProxySuper.WPF/ProxySuper.WPF.csproj b/ProxySuper.WPF/ProxySuper.WPF.csproj index de1918d..a91f06c 100644 --- a/ProxySuper.WPF/ProxySuper.WPF.csproj +++ b/ProxySuper.WPF/ProxySuper.WPF.csproj @@ -10,7 +10,7 @@ AnyCPU true full - true + false bin\Debug\ TRACE prompt @@ -28,6 +28,9 @@ ProxySU.ico + + ProxySuper.WPF.App + ..\packages\MvvmCross.7.1.2\lib\net461\MvvmCross.dll @@ -75,6 +78,9 @@ HostControl.xaml + + ProgressControl.xaml + ShadowSocksControl.xaml @@ -156,10 +162,17 @@ XrayInstallerView.xaml + + XrayInstallView.xaml + Designer MSBuild:Compile + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -292,6 +305,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + diff --git a/ProxySuper.WPF/Views/BrookInstallerView.xaml.cs b/ProxySuper.WPF/Views/BrookInstallerView.xaml.cs index 071fa64..e39d7fc 100644 --- a/ProxySuper.WPF/Views/BrookInstallerView.xaml.cs +++ b/ProxySuper.WPF/Views/BrookInstallerView.xaml.cs @@ -84,7 +84,7 @@ namespace ProxySuper.WPF.Views auth = new PrivateKeyAuthenticationMethod(host.UserName, new PrivateKeyFile(host.PrivateKeyPath)); } - if (host.Proxy.Type == LocalProxyType.None) + if (host.Proxy.Type == ProxyTypes.None) { return new ConnectionInfo(host.Address, host.Port, host.UserName, auth); } @@ -94,7 +94,7 @@ namespace ProxySuper.WPF.Views host: host.Address, port: host.Port, username: host.UserName, - proxyType: (ProxyTypes)(int)host.Proxy.Type, + proxyType: host.Proxy.Type, proxyHost: host.Proxy.Address, proxyPort: host.Proxy.Port, proxyUsername: host.Proxy.UserName, diff --git a/ProxySuper.WPF/Views/NaiveProxyInstallerView.xaml.cs b/ProxySuper.WPF/Views/NaiveProxyInstallerView.xaml.cs index 991b70a..05aa96d 100644 --- a/ProxySuper.WPF/Views/NaiveProxyInstallerView.xaml.cs +++ b/ProxySuper.WPF/Views/NaiveProxyInstallerView.xaml.cs @@ -93,7 +93,7 @@ namespace ProxySuper.WPF.Views auth = new PrivateKeyAuthenticationMethod(host.UserName, new PrivateKeyFile(host.PrivateKeyPath)); } - if (host.Proxy.Type == LocalProxyType.None) + if (host.Proxy.Type == ProxyTypes.None) { return new ConnectionInfo(host.Address, host.Port, host.UserName, auth); } @@ -103,7 +103,7 @@ namespace ProxySuper.WPF.Views host: host.Address, port: host.Port, username: host.UserName, - proxyType: (ProxyTypes)(int)host.Proxy.Type, + proxyType: host.Proxy.Type, proxyHost: host.Proxy.Address, proxyPort: host.Proxy.Port, proxyUsername: host.Proxy.UserName, diff --git a/ProxySuper.WPF/Views/TrojanGoInstallerView.xaml.cs b/ProxySuper.WPF/Views/TrojanGoInstallerView.xaml.cs index d7abff7..5b404d9 100644 --- a/ProxySuper.WPF/Views/TrojanGoInstallerView.xaml.cs +++ b/ProxySuper.WPF/Views/TrojanGoInstallerView.xaml.cs @@ -90,7 +90,7 @@ namespace ProxySuper.WPF.Views auth = new PrivateKeyAuthenticationMethod(host.UserName, new PrivateKeyFile(host.PrivateKeyPath)); } - if (host.Proxy.Type == LocalProxyType.None) + if (host.Proxy.Type == ProxyTypes.None) { return new ConnectionInfo(host.Address, host.Port, host.UserName, auth); } @@ -100,7 +100,7 @@ namespace ProxySuper.WPF.Views host: host.Address, port: host.Port, username: host.UserName, - proxyType: (ProxyTypes)(int)host.Proxy.Type, + proxyType: host.Proxy.Type, proxyHost: host.Proxy.Address, proxyPort: host.Proxy.Port, proxyUsername: host.Proxy.UserName, diff --git a/ProxySuper.WPF/Views/XrayInstallView.xaml b/ProxySuper.WPF/Views/XrayInstallView.xaml new file mode 100644 index 0000000..3a1dd0e --- /dev/null +++ b/ProxySuper.WPF/Views/XrayInstallView.xaml @@ -0,0 +1,38 @@ + + + + + +