From 38e44f7b4adcea80cfb5060ec83924184bb659a5 Mon Sep 17 00:00:00 2001 From: W11 Date: Wed, 23 Sep 2026 19:39:08 +0800 Subject: [PATCH] =?UTF-8?q?fix(remote):=20VNC=20=E5=8D=87=E7=BA=A7?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=9C=80=E6=96=B0=E7=89=88=20TightVNC?= =?UTF-8?q?=E2=80=94=E2=80=94=E4=BF=AE=20ReadVncServerConfig=20=E7=9A=84?= =?UTF-8?q?=20ConnectToRdp=20=E8=AF=AF=E8=AF=BB=20ExtraPorts=E3=80=81?= =?UTF-8?q?=E8=A1=A5=20WriteVncServerConfig=20=E6=BC=8F=E5=86=99=E7=9A=84?= =?UTF-8?q?=20LogLevel(=E8=AF=BB/=E5=86=99=E5=9D=87=E8=BE=BE=2037=20?= =?UTF-8?q?=E9=A1=B9=E5=85=A8=E9=9B=86)=E3=80=81InstallVncServer=20?= =?UTF-8?q?=E4=BB=8E=E5=9B=BA=E5=AE=9A=E5=AF=86=E7=A0=81=E7=A9=BA=E5=A3=B3?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=B8=BA=E7=9C=9F=E5=AE=89=E8=A3=85(?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=20exe=E2=86=92=E5=86=99=E6=9C=80=E6=96=B0?= =?UTF-8?q?=E7=89=88=E5=85=A8=E9=9B=86=E9=85=8D=E7=BD=AE+=E9=9A=8F?= =?UTF-8?q?=E6=9C=BA=E5=AF=86=E7=A0=81=E2=86=92=E6=B3=A8=E5=86=8C=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1,=20=E8=A3=85=E8=80=8C=E4=B8=8D=E5=90=AF/=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E4=B8=AD=E9=87=8D=E5=90=AF,=20=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E6=98=8E=E6=96=87=E5=AF=86=E7=A0=81);=20=E9=94=81=E5=AE=9A?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E7=89=88=E9=BB=98=E8=AE=A4=E5=80=BC=E4=B8=8E?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E5=BC=82=E5=B8=B8=E8=B7=AF=E5=BE=84=E7=9A=84?= =?UTF-8?q?=E5=8D=95=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- remote/README.md | 7 +++- remote/remote_test.go | 42 +++++++++++++++++++++ remote/vnc.go | 87 +++++++++++++++++++------------------------ remote/windows.go | 4 ++ 4 files changed, 90 insertions(+), 50 deletions(-) diff --git a/remote/README.md b/remote/README.md index 3f2be68..a0e96db 100644 --- a/remote/README.md +++ b/remote/README.md @@ -31,7 +31,10 @@ pass, err := remote.GetVncPassword() cfg := remote.VncServerDefaultConfig(true /* 随机密码 */) // VncServerConfig err = remote.WriteVncServerConfig(cfg) cfg, err = remote.ReadVncServerConfig() -_, msg, err := remote.InstallVncServer() // 安装服务(需管理员权限) +// 绿色安装最新版 TightVNC:校验 exe → 写注册表全集(随机密码, 需管理员权限) → +// 注册系统服务(装而不启, 远程时再 VncStart; 已运行则重启加载新配置)。 +// 返回生成的明文密码供调用方上报/展示。 +pass, err := remote.InstallVncServer(`D:\app\tvnc\tvnserver.exe`) err = remote.VncInit() // 初始化/修复配置 err = remote.VncStart() // VncStop / VncRestart err = remote.VncInstall() // VncUninstall @@ -55,6 +58,6 @@ err = remote.EnableSoftwareSASGeneration() - `GetServiceStatus` 系列对未安装服务返回 -1 而非错误;`GetRdpServiceStatus` / `GetVncServiceStatus` 走 `kardianos/service`,未安装返回 error。 -- `InstallVncServer` / `VncInit` / CAD 开关需要管理员权限,失败信息见返回 msg 或 error。 +- `InstallVncServer` / `VncInit` / CAD 开关需要管理员权限,失败信息见返回 error;安装配置对齐最新版 TightVNC(2.8.x) 注册表 37 项全集。 - VNC 密码格式与 TightVNC 兼容(`KarpelesLab/vncpasswd` 实现),加解密为确定性 变换,注意密文落盘位置的访问控制。 diff --git a/remote/remote_test.go b/remote/remote_test.go index d810482..daeffa1 100644 --- a/remote/remote_test.go +++ b/remote/remote_test.go @@ -49,3 +49,45 @@ func TestVncServerDefaultConfig(t *testing.T) { t.Error("HttpPort 不应为 0") } } + +// TestVncClientDefaultConfigLatest 锁定对最新版 TightVNC(2.8.x) 注册表全集的支持: +// 值与本机最新版安装器落盘值逐项对齐(AcceptHttpConnections/RunControlInterface +// 为安全策略有意取 0:不开 5800 Web 通道、不显托盘)。 +func TestVncClientDefaultConfigLatest(t *testing.T) { + cfg := VncClientDefaultConfig(false) + if cfg.RfbPort != 5900 || cfg.HttpPort != 5800 { + t.Errorf("端口 = %d/%d, want 5900/5800", cfg.RfbPort, cfg.HttpPort) + } + // 最新版捕获能力:镜像驱动 + D3D + 透明窗口 + 视频区域识别 + if cfg.UseMirrorDriver != 1 || cfg.UseD3D != 1 { + t.Errorf("捕获开关 = mirror:%d d3d:%d, want 1/1", cfg.UseMirrorDriver, cfg.UseD3D) + } + if cfg.GrabTransparentWindows != 1 || cfg.VideoRecognitionInterval != 3000 { + t.Errorf("透明窗口/视频识别 = %d/%d, want 1/3000", cfg.GrabTransparentWindows, cfg.VideoRecognitionInterval) + } + // 安全策略:不开 HTTP 通道、不出托盘控制台、不接 RDP 会话 + if cfg.AcceptHttpConnections != 0 || cfg.RunControlInterface != 0 || cfg.ConnectToRdp != 0 { + t.Errorf("安全策略 = http:%d tray:%d rdp:%d, want 0/0/0", cfg.AcceptHttpConnections, cfg.RunControlInterface, cfg.ConnectToRdp) + } + // 连接行为:VNC 认证 + 接受 RFB + 本机输入优先 + 断开踢旧连接 + if cfg.AcceptRfbConnections != 1 || cfg.UseVncAuthentication != 1 || cfg.LocalInputPriority != 1 || cfg.DisconnectClients != 1 { + t.Errorf("连接行为异常: %+v", cfg) + } + if cfg.QueryTimeout != 30 || cfg.LocalInputPriorityTimeout != 3 || cfg.PollingInterval != 1000 || cfg.AllowLoopback != 1 { + t.Errorf("超时/轮询/环回异常: %+v", cfg) + } + if len(cfg.Password) == 0 { + t.Error("固定密码密文不应为空") + } + if dec := VncPasswordDecrypt(cfg.Password); dec != "tvncpass" { + t.Errorf("固定密码回解 = %q, want tvncpass", dec) + } +} + +// TestInstallVncServerMissingExe 安装入口对 tvnserver.exe 缺失必须报错(不触注册表/服务) +func TestInstallVncServerMissingExe(t *testing.T) { + _, err := InstallVncServer(`Z:\no\such\dir\tvnserver.exe`) + if err == nil { + t.Fatal("exe 不存在时应返回错误") + } +} diff --git a/remote/vnc.go b/remote/vnc.go index eaa1c77..eb100bf 100644 --- a/remote/vnc.go +++ b/remote/vnc.go @@ -3,6 +3,9 @@ package remote import ( + "fmt" + "os" + "github.com/KarpelesLab/vncpasswd" "github.com/kardianos/service" "golang.org/x/sys/windows/registry" @@ -10,47 +13,9 @@ import ( var VncService service.Service -// ReadVncServerConfig 从注册表读取 TightVNC 服务端配置 +// ReadVncServerConfig 从注册表读取 TightVNC 服务端配置(键值缺失按零值返回) func ReadVncServerConfig() (VncServerConfig, error) { - config := VncServerConfig{ - ExtraPorts: "", - QueryTimeout: 0, - QueryAcceptOnTimeout: 0, - LocalInputPriorityTimeout: 0, - LocalInputPriority: 0, - BlockRemoteInput: 0, - BlockLocalInput: 0, - IpAccessControl: "", - RfbPort: 0, - HttpPort: 0, - DisconnectAction: 0, - AcceptRfbConnections: 0, - UseVncAuthentication: 0, - UseControlAuthentication: 0, - RepeatControlAuthentication: 0, - LoopbackOnly: 0, - AcceptHttpConnections: 0, - LogLevel: 0, - EnableFileTransfers: 0, - RemoveWallpaper: 0, - UseD3D: 0, - UseMirrorDriver: 0, - EnableUrlParams: 0, - Password: []byte{}, - AlwaysShared: 0, - NeverShared: 0, - DisconnectClients: 0, - PollingInterval: 0, - AllowLoopback: 0, - VideoRecognitionInterval: 0, - GrabTransparentWindows: 0, - SaveLogToAllUsersPath: 0, - RunControlInterface: 0, - IdleTimeout: 0, - VideoClasses: "", - VideoRects: "", - ConnectToRdp: 0, - } + var config VncServerConfig key, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\TightVNC\\Server", registry.ALL_ACCESS) if err != nil { @@ -93,7 +58,7 @@ func ReadVncServerConfig() (VncServerConfig, error) { config.IdleTimeout, _, _ = GetDwordValue(key, "IdleTimeout") config.VideoClasses, _, _ = key.GetStringValue("VideoClasses") config.VideoRects, _, _ = key.GetStringValue("VideoRects") - config.ConnectToRdp, _, _ = GetDwordValue(key, "ExtraPorts") + config.ConnectToRdp, _, _ = GetDwordValue(key, "ConnectToRdp") return config, nil } @@ -109,16 +74,42 @@ func GetDwordValue(k registry.Key, name string) (val uint32, valtype uint32, err var vncSvcConfig = &service.Config{ Name: "tvnserver", DisplayName: "TightVNC Server", - Description: "", + Description: "TightVNC Server (managed by remote-core)", Arguments: []string{"-service"}, } -// InstallVncServer 安装VNC服务端 返回:结果,密码,错误 -func InstallVncServer() (bool, string, error) { - //安装服务 - //导入注册表 - //生成随机密码 - return true, "12345678", nil +// InstallVncServer 绿色安装 TightVNC 服务端(需管理员权限)。 +// 最新版注册表全集配置(VncClientDefaultConfig 37 项, 随机 8 位密码)→ 注册系统服务。 +// 装而不启:服务注册后保持 Stopped, 等远程指令再 VncStart(避免常驻监听开端口); +// 若服务已在运行则重启以加载新配置。返回生成的明文密码(调用方自行上报/展示)。 +func InstallVncServer(exePath string) (string, error) { + if finfo, err := os.Stat(exePath); err != nil || finfo.IsDir() { + return "", fmt.Errorf("tvnserver.exe 不存在: %s", exePath) + } + + // 最新版 TightVNC 注册表配置全集(含随机密码) + config := VncClientDefaultConfig(true) + if err := WriteVncServerConfig(config); err != nil { + return "", err + } + + // 注册系统服务:未安装→安装; 运行中→重启加载新配置; 已停止→保持 + vncSvcConfig.Executable = exePath + if err := VncInit(); err != nil { + return "", err + } + status, err := VncService.Status() + switch { + case err != nil: // 未安装(kardianos 对未安装服务 Status 返回错误) + if err := VncInstall(); err != nil { + return "", err + } + case status == service.StatusRunning: + if err := VncRestart(); err != nil { + return "", err + } + } + return VncPasswordDecrypt(config.Password), nil } // VncInit 初始化 VNC 服务对象,VncInstall/VncStart 等操作前必须调用 diff --git a/remote/windows.go b/remote/windows.go index 8c495e8..6481408 100644 --- a/remote/windows.go +++ b/remote/windows.go @@ -280,6 +280,10 @@ func WriteVncServerConfig(config VncServerConfig) error { if err != nil { return err } + err = key.SetDWordValue("LogLevel", config.LogLevel) + if err != nil { + return err + } err = key.SetDWordValue("EnableFileTransfers", config.EnableFileTransfers) if err != nil { return err