diff --git a/guide/network/install-as-a-windows-service.md b/guide/network/install-as-a-windows-service.md index bb5084f..8e8f48a 100644 --- a/guide/network/install-as-a-windows-service.md +++ b/guide/network/install-as-a-windows-service.md @@ -1,8 +1,10 @@ # 安装为 Windows 服务 -> 感谢 北辰℃ 提供的教程 +> 感谢 北辰℃ 提供的教程,以及由 dawn-lc 提供的一键安装/卸载脚本 -在 Windows 系统中,将某些应用程序安装为服务可以使其在后台自动运行,无需用户手动干预,极大地提高了应用的运行稳定性和便捷性。本教程将以使用 NSSM(Non-Sucking Service Manager)工具将 EasyTier 应用安装为 Windows 服务为例,详细介绍整个操作流程。 +在 Windows 系统中,将某些应用程序安装为服务可以使其在后台自动运行,无需用户手动干预,极大地提高了应用的运行稳定性和便捷性。 + +本教程将以使用 NSSM(Non-Sucking Service Manager)工具将 EasyTier 应用安装为 Windows 服务为例,详细介绍整个操作流程。 ## 一、前期准备 @@ -12,6 +14,12 @@ 下载完成后,将该压缩包解压到本地目录,比如`D:\EasyTier`。 +当前目录下应包含以下文件: + - `easytier-core.exe` (核心程序) + - `easytier-cli.exe` (命令行工具) + - `Packet.dll` (运行库) + - `wintun.dll` (运行库) + **下载 NSSM**: 打开浏览器,访问 NSSM 官网 [https://nssm.cc/](https://nssm.cc/download)。 @@ -33,7 +41,7 @@ 在当前目录下创建`install.cmd`文件并写入以下内容: -```Batch +```PowerShell @echo off @chcp 65001 > nul cd /d "%~dp0" @@ -43,97 +51,281 @@ if %ERRORLEVEL% NEQ 0 echo PowerShell is not installed. && pause > nul && exit powershell -command "if ($PSVersionTable.PSVersion.Major -lt 3) {throw 'Requires PowerShell 3.0 or higher.'}; $content = Get-Content -Path '%0' -Raw -Encoding UTF8; $mainIndex = $content.LastIndexOf('#powershell#'); if ($mainIndex -le 0) {throw 'PowerShell script not found.'}; $content = $content.Substring($mainIndex + '#powershell#'.Length); $content = [ScriptBlock]::Create($content); Invoke-Command -ScriptBlock $content -ArgumentList (('%*' -split ' ') + @((Get-Location).Path));" exit #powershell# -function Pause { - param ( - [string]$Text = "按任意键继续..." +[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo]::GetCultureInfo("zh-CN") +[System.Threading.Thread]::CurrentThread.CurrentUICulture = [System.Globalization.CultureInfo]::GetCultureInfo("zh-CN") +function Show-Pause { + param( + [string]$Text = "按任意键继续...", + [string]$Color = "Cyan" ) - Write-Host $Text -ForegroundColor Yellow + Write-Host "`n$Text" -ForegroundColor $Color [System.Console]::ReadKey($true) > $null } +function Show-YesNoPrompt { + param( + [Parameter(Mandatory = $true)] + [string]$Message, + [string]$Title = "", + [int]$DefaultIndex = 0, + [string[]]$Labels = @("&Yes", "&No"), + [string[]]$Helps = @("是", "否") + ) + if ($Labels.Count -ne $Helps.Count) { + throw "Labels 和 Helps 的数量必须相同。" + } + $choices = for ($i = 0; $i -lt $Labels.Count; $i++) { + [System.Management.Automation.Host.ChoiceDescription]::new($Labels[$i], $Helps[$i]) + } + return $Host.UI.PromptForChoice($Title, $Message, $choices, $DefaultIndex) -eq 0 +} +function Show-MultipleChoicePrompt { + param( + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter(Mandatory = $true)] + [string[]]$Options, + [string[]]$Helps = @(), + [string]$Title = "", + [int]$DefaultIndex = 0 + ) + if ($Helps.Count -eq 0) { + $Helps = @("") + for ($i = 1; $i -lt $Options.Count; $i++) { + $Helps += "" + } + } + if ($Options.Count -ne $Helps.Count) { + throw "Options 和 Helps 的数量必须相同。" + } + $choices = for ($i = 0; $i -lt $Options.Count; $i++) { + [System.Management.Automation.Host.ChoiceDescription]::new("&$i.$($Options[$i])", $Helps[$i]) + } + return $Host.UI.PromptForChoice($Title, $Message, $choices, $DefaultIndex) +} +function Get-InputWithNoNullOrWhiteSpace { + param( + [string]$Prompt + ) + while ($true) { + $response = Read-Host "请输入${Prompt}(必填)" + if ([string]::IsNullOrWhiteSpace($response)) { + Write-Host "${Prompt}不能为空!" -ForegroundColor Red + } + else { + return $response + } + } +} +function Get-InputWithDefault { + param( + [Parameter(Mandatory = $true)] + [string]$Prompt, -# 初始化路径 + [Parameter(Mandatory = $true)] + [string]$DefaultValue + ) + $response = Read-Host "${Prompt}(默认: ${DefaultValue})" + if ([string]::IsNullOrWhiteSpace($response)) { + return $DefaultValue + } + return $response +} Set-Location -Path $args[-1] $ScriptRoot = (Get-Location).Path - -# 修改标题 -$host.ui.rawui.WindowTitle = "安装EasyTierService" - -# 检查管理员权限 +$host.ui.rawui.WindowTitle = "安装EasyTier服务" if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Host "请使用管理员权限运行!" -ForegroundColor Red - Pause -Text "按任意键退出..." + Show-Pause -Text "按任意键退出..." exit 1 } - -# 必要文件检查 -$RequiredFiles = @("nssm.exe", "easytier-core.exe", "easytier-cli.exe") +$RequiredFiles = @("easytier-core.exe", "easytier-cli.exe", "nssm.exe", "Packet.dll", "wintun.dll") foreach ($file in $RequiredFiles) { if (-not (Test-Path (Join-Path $ScriptRoot $file))) { - Write-Host "缺少必要文件: $file" -ForegroundColor Red - Pause -Text "按任意键退出..." + Write-Host "缺少必要文件: ${file}" -ForegroundColor Red + Show-Pause -Text "按任意键退出..." exit 1 } } - -# 交互式配置部分 Write-Host "`n正在创建EasyTier服务配置...`n" -ForegroundColor Cyan - -# 服务名称设置 $SERVICE_NAME = "EasyTierService" - -# 网络名称验证 -do { - $NETWORK_NAME = Read-Host "请输入网络名称(必填)" -} while ([string]::IsNullOrWhiteSpace($NETWORK_NAME)) - -# 网络密钥验证 -do { - $NETWORK_SECRET = Read-Host "请输入网络密钥(必填)" -} while ([string]::IsNullOrWhiteSpace($NETWORK_SECRET)) - -# 中继节点处理 -$PEERS = Read-Host "请输入中继节点(多个节点用英文逗号分隔,默认tcp://public.easytier.cn:11010)" -$PEERS = if ([string]::IsNullOrWhiteSpace($PEERS)) { "tcp://public.easytier.cn:11010" } else { $PEERS } -$PEERS_PARAMS = ($PEERS -split ',' | ForEach-Object { "--peers $($_.Trim())" }) -join ' ' - -# 设备名称设置 -$DEV_NAME = Read-Host "请输入TUN设备名称(默认EasyTierNET)" -$DEV_NAME = if ([string]::IsNullOrEmpty($DEV_NAME)) { "EasyTierNET" } else { $DEV_NAME } - -# 功能选项配置 $OPTIONS = @() -if ((Read-Host "是否启用低延迟优先模式[Y/n]") -match '^[Yy]?$') { $OPTIONS += "--latency-first" } -if ((Read-Host "是否启用多线程模式[Y/n]") -match '^[Yy]?$') { $OPTIONS += "--multi-thread" } -if ((Read-Host "是否启用KCP代理[Y/n]") -match '^[Yy]?$') { $OPTIONS += "--enable-kcp-proxy" } -if ((Read-Host "是否启用系统代理转发[Y/n]") -match '^[Yy]?$') { $OPTIONS += "--proxy-forward-by-system" } +if (Show-YesNoPrompt -Message "是否使用配置文件方案?" -DefaultIndex 1) { + $OPTIONS += "--config-file $(Get-InputWithNoNullOrWhiteSpace -Prompt "配置文件路径")" +} +elseif (Show-YesNoPrompt -Message "是否使用配置服务器?" -DefaultIndex 1) { + $configServer = Get-InputWithDefault -Prompt "配置服务器地址(格式:协议://IP:端口/用户)" -DefaultValue "udp://127.0.0.1:22020/admin" + $OPTIONS += "--config-server $configServer" +} +else { + $OPTIONS += "--network-name $(Get-InputWithNoNullOrWhiteSpace -Prompt "网络名称")" + $OPTIONS += "--network-secret $(Get-InputWithNoNullOrWhiteSpace -Prompt "网络密钥")" + if (Show-YesNoPrompt -Message "是否指定当前设备名称?" -DefaultIndex 1) { + $OPTIONS += "--hostname $(Get-InputWithNoNullOrWhiteSpace -Prompt "设备名称")" + } + if (Show-YesNoPrompt -Message "是否添加公共节点?") { + $OPTIONS += "--external-node $(Get-InputWithDefault -Prompt "公共节点地址" -DefaultValue "tcp://public.easytier.cn:11010")" + } + if (Show-YesNoPrompt -Message "是否添加对等节点?") { + $peers = @() + do { + $peers += Get-InputWithDefault -Prompt "对等节点地址" -DefaultValue "tcp://public.easytier.cn:11010" + } while (Show-YesNoPrompt -Message "是否继续添加对等节点?" -DefaultIndex 1) + if ($peers.Count -gt 0) { + $OPTIONS += ($peers | ForEach-Object { "--peers $($_.Trim())" }) -join ' ' + } + } + $ipChoice = Show-MultipleChoicePrompt -Message "请选择IP分配方式" ` + -Options @("手动指定IPv4", "自动DHCP", "不设置IP") ` + -Helps @("指定当前设备在网络中的IP地址", "自动分配网络中的地址", "将仅转发数据包,不会创建TUN设备") ` + -DefaultIndex 1 + switch ($ipChoice) { + 0 { + $OPTIONS += "--ipv4 $(Get-InputWithNoNullOrWhiteSpace -Prompt "IPv4地址")" + break + } + 1 { + $OPTIONS += "--dhcp" + break + } + 2 { break } + } + if (Show-YesNoPrompt -Message "是否启用端口监听?" -DefaultIndex 1) { + $listeners = @() + do { + $listener = Get-InputWithDefault -Prompt "监听器地址(格式:协议://IP:端口)" -DefaultValue "11010" + $listeners += $listener + } while (Show-YesNoPrompt -Message "是否添加更多监听器?" -DefaultIndex 1) + $OPTIONS += "--listeners $($listeners -join ' ')" -# 服务安装部分 + if (Show-YesNoPrompt -Message "是否手动指定公网映射地址?") { + $mapped = Get-InputWithNoNullOrWhiteSpace -Prompt "公网地址(格式:协议://IP:端口)" + $OPTIONS += "--mapped-listeners $mapped" + } + } + else { + $OPTIONS += "--no-listener" + } + if (Show-YesNoPrompt -Message "是否启用多线程运行?") { + $OPTIONS += "--multi-thread" + } + if (Show-YesNoPrompt -Message "是否启用延迟优先模式?") { + $OPTIONS += "--latency-first" + } + if (Show-YesNoPrompt -Message "是否通过系统内核转发?") { + $OPTIONS += "--proxy-forward-by-system" + } + if (Show-YesNoPrompt -Message "是否启用KCP代理?") { + $OPTIONS += "--enable-kcp-proxy" + } + if (Show-YesNoPrompt -Message "是否指定TUN设备名称?" -DefaultIndex 1) { + $OPTIONS += "--dev-name $(Get-InputWithNoNullOrWhiteSpace -Prompt "设备名称")" + } + if (Show-YesNoPrompt -Message "是否设置转发网络白名单?" -DefaultIndex 1) { + $whitelist = Get-InputWithDefault -Prompt "白名单网络(空格分隔,*表示全部)" -DefaultValue "*" + $OPTIONS += "--relay-network-whitelist `"$whitelist`"" + } + if (Show-YesNoPrompt -Message "是否调整高级选项?" -DefaultIndex 1) { + if (Show-YesNoPrompt -Message "是否开启日志?" -DefaultIndex 1) { + $logChoice = Show-MultipleChoicePrompt -Message "请选择日志级别" -Options @("DEBUG", "INFO", "WARN", "ERROR") + $logLevels = @("debug", "info", "warn", "error") + $OPTIONS += "--console-log-level $($logLevels[$logChoice])" + } + if (Show-YesNoPrompt -Message "是否修改RPC配置?" -DefaultIndex 1) { + $OPTIONS += "--rpc-portal $(Get-InputWithNoNullOrWhiteSpace -Prompt "RPC配置(格式:监听地址:端口)")" + } + if (Show-YesNoPrompt -Message "是否修改默认协议?" -DefaultIndex 1) { + $protoChoice = Show-MultipleChoicePrompt -Message "请选择默认协议" ` + -Options @("TCP", "UDP", "WebSocket", "WireGuard") ` + -Helps @("可靠传输,适合高延迟网络", "低延迟,适合稳定网络", "穿透性强,适合受限网络", "高性能VPN协议") ` + -DefaultIndex 0 + $protocols = @("tcp", "udp", "ws", "wg") + $OPTIONS += "--default-protocol $($protocols[$protoChoice])" + } + if (Show-YesNoPrompt -Message "是否禁用IPv6?" -DefaultIndex 1) { + $OPTIONS += "--disable-ipv6" + } + if (Show-YesNoPrompt -Message "是否启用出口节点?" -DefaultIndex 1) { + $OPTIONS += "--enable-exit-node" + } + if (Show-YesNoPrompt -Message "是否启用SOCKS5代理?" -DefaultIndex 1) { + $OPTIONS += "--socks5 $(Get-InputWithDefault -Prompt "SOCKS5端口" -DefaultValue "1080")" + } + if (Show-YesNoPrompt -Message "是否设置自定义MTU?" -DefaultIndex 1) { + $OPTIONS += "--mtu $(Get-InputWithDefault -Prompt "MTU值" -DefaultValue "1380")" + } + if (Show-YesNoPrompt -Message "是否导出本地网络到VPN?" -DefaultIndex 1) { + $proxyNets = @() + do { + $proxyNets += Get-InputWithDefault -Prompt "本地网络CIDR(如192.168.1.0/24)" -DefaultValue "192.168.0.0/24" + } while (Show-YesNoPrompt -Message "是否添加更多本地网络?") + $OPTIONS += "--proxy-networks $($proxyNets -join ' ')" + } + if (Show-YesNoPrompt -Message "是否设置实例名称?" -DefaultIndex 1) { + $OPTIONS += "--instance-name $(Get-InputWithDefault -Prompt "实例名称" -DefaultValue "default")" + } + if (Show-YesNoPrompt -Message "是否设置VPN地址?" -DefaultIndex 1) { + $vpnPortal = Get-InputWithNoNullOrWhiteSpace -Prompt "VPN地址(格式:协议://IP:端口/网络)" + $OPTIONS += "--vpn-portal $vpnPortal" + } + if (Show-YesNoPrompt -Message "是否禁用通信加密?" -DefaultIndex 1) { + $OPTIONS += "--disable-encryption" + } + if (Show-YesNoPrompt -Message "是否禁用TUN设备?" -DefaultIndex 1) { + $OPTIONS += "--no-tun" + } + if (Show-YesNoPrompt -Message "是否使用smoltcp协议栈?" -DefaultIndex 1) { + $OPTIONS += "--use-smoltcp" + } + if (Show-YesNoPrompt -Message "是否转发所有RPC流量?" -DefaultIndex 1) { + $OPTIONS += "--relay-all-peer-rpc" + } + if (Show-YesNoPrompt -Message "是否禁用KCP输入?" -DefaultIndex 1) { + $OPTIONS += "--disable-kcp-input" + } + if (Show-YesNoPrompt -Message "是否绑定到物理设备?" -DefaultIndex 1) { + $OPTIONS += "--bind-device true" + } + if (Show-YesNoPrompt -Message "是否禁用P2P通信?" -DefaultIndex 1) { + $OPTIONS += "--disable-p2p" + } + if (Show-YesNoPrompt -Message "是否禁用UDP打洞?" -DefaultIndex 1) { + $OPTIONS += "--disable-udp-hole-punching" + } + if (Show-YesNoPrompt -Message "是否配置压缩算法?" -DefaultIndex 1) { + $compressChoice = Show-MultipleChoicePrompt -Message "请选择压缩算法" ` + -Options @("不压缩", "ZSTD") ` + -DefaultIndex 0 + $compressAlgos = @("none", "zstd") + $OPTIONS += "--compression $($compressAlgos[$compressChoice])" + } + if (Show-YesNoPrompt -Message "是否设置手动路由?" -DefaultIndex 1) { + $routes = @() + do { + $routes += Get-InputWithDefault -Prompt "路由CIDR(如10.0.0.0/8)" -DefaultValue "10.0.0.0/8" + } while (Show-YesNoPrompt -Message "是否添加更多路由?" -DefaultIndex 1) + $OPTIONS += "--manual-routes $($routes -join ' ')" + } + } +} try { $nssm = Join-Path $ScriptRoot "nssm.exe" - - # 创建服务 - & $nssm install $SERVICE_NAME (Join-Path $ScriptRoot "easytier-core.exe") - - # 配置服务参数 - $arguments = @( - "-d", - "--dev-name $DEV_NAME", - "--no-listener", - $OPTIONS, - "--network-name $NETWORK_NAME", - "--network-secret $NETWORK_SECRET", - $PEERS_PARAMS - ) -join ' ' - - & $nssm set $SERVICE_NAME AppParameters $arguments - & $nssm set $SERVICE_NAME Description "EasyTier 核心服务" - & $nssm set $SERVICE_NAME AppDirectory $ScriptRoot - & $nssm set $SERVICE_NAME Start SERVICE_AUTO_START - - # 启动服务 - & $nssm start $SERVICE_NAME - - Write-Host "`n服务安装完成,如需查看节点信息请执行:easytier-cli.exe node" -ForegroundColor Green + $arguments = $OPTIONS -join ' ' + Write-Host "`n生成的配置参数如下:" -ForegroundColor Yellow + Write-Host ($OPTIONS -join " ") -ForegroundColor DarkGray + if (Show-YesNoPrompt -Message "`n确认安装配置?" -DefaultIndex 1) { + & $nssm install $SERVICE_NAME (Join-Path $ScriptRoot "easytier-core.exe") + & $nssm set $SERVICE_NAME AppParameters $arguments + & $nssm set $SERVICE_NAME Description "EasyTier 核心服务" + & $nssm set $SERVICE_NAME AppDirectory $ScriptRoot + & $nssm set $SERVICE_NAME Start SERVICE_AUTO_START + & $nssm start $SERVICE_NAME + Write-Host "`n服务安装完成,如需查看节点信息请执行:easytier-cli.exe node" -ForegroundColor Green + } + else { + Write-Host "安装已取消。" -ForegroundColor Yellow + Show-Pause -Text "按任意键退出..." + exit + } } catch { Write-Host "`n安装过程中发生错误: $_" -ForegroundColor Red @@ -146,7 +338,7 @@ exit 在当前目录下创建`uninstall.cmd`文件并写入以下内容: -```Batch +```PowerShell @echo off @chcp 65001 > nul cd /d "%~dp0" @@ -179,7 +371,7 @@ if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdenti } # 必要文件检查 -$RequiredFiles = @("nssm.exe", "easytier-core.exe", "easytier-cli.exe") +$RequiredFiles = @("nssm.exe") foreach ($file in $RequiredFiles) { if (-not (Test-Path (Join-Path $ScriptRoot $file))) { Write-Host "缺少必要文件: $file" -ForegroundColor Red @@ -243,4 +435,5 @@ exit ## 六、常见问题 **Q: 如何修改服务配置?** + A: 先卸载服务,然后重新安装