Files
kevin 3c076785f6 feat: Windows 安装器 + 修复 daemon 无法连接问题
- 新增 Inno Setup 安装器脚本 (installer/lmvpn.iss),支持自定义安装路径、
  桌面快捷方式、安装前杀死旧进程,打包 wintun.dll
- Makefile 新增 installer-windows 目标,支持 ISCC 和 Wine 两种编译方式
- Windows IPC 从 AF_UNIX 改为 TCP (127.0.0.1:18923),解决提权 daemon 与
  非提权 GUI 之间的完整性级别限制导致 daemon did not become reachable
- 修复 elevation_windows.go 中 ShellExecute 参数路径含空格时被截断的问题
  (C:\Program Files\LMVPN\lmvpnd.exe → 只取到 C:\Program)
- ensureDaemon 重试次数 40→60,添加连接失败日志便于诊断
- 新增 IPCNetwork()/IPCAddress() 跨平台函数,统一 IPC 传输层抽象
2026-07-07 19:29:41 +08:00

146 lines
4.7 KiB
Plaintext

; LMVPN Windows Installer — Inno Setup script
;
; Build:
; ISCC /DAppVersion=0.3.6-abc123 installer/lmvpn.iss
; wine "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" /DAppVersion=0.3.6-abc123 installer/lmvpn.iss
;
; The exes (lmvpn.exe, lmvpnd.exe) must exist in ../build/ before compiling.
; Run `make build-windows` first (or `make installer-windows` which does both).
#ifndef AppVersion
#define AppVersion "dev"
#endif
#define AppName "LMVPN"
#define AppPublisher "LMVPN"
#define AppExeName "lmvpn.exe"
#define DaemonExeName "lmvpnd.exe"
#define AppURL "https://github.com/lmvpn/lmvpn_client"
[Setup]
AppId={{8F7B3A2E-1D4C-4B5E-9F6A-2E3C7D8E1F0A}
AppName={#AppName}
AppVersion={#AppVersion}
AppVerName={#AppName} {#AppVersion}
AppPublisher={#AppPublisher}
AppPublisherURL={#AppURL}
AppSupportURL={#AppURL}
AppUpdatesURL={#AppURL}
DefaultDirName={autopf}\{#AppName}
DefaultGroupName={#AppName}
DisableProgramGroupPage=yes
PrivilegesRequired=admin
ArchitecturesAllowed=x64compatible
ArchitecturesInstallIn64BitMode=x64compatible
Compression=lzma2
SolidCompression=yes
WizardStyle=modern
OutputDir=..\build
OutputBaseFilename=LMVPN-Setup-{#AppVersion}
SetupIconFile=..\resources\icon.ico
UninstallDisplayIcon={app}\{#AppExeName}
UninstallDisplayName={#AppName}
[Languages]
Name: "chinesesimp"; MessagesFile: "ChineseSimplified.isl"
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
Name: "desktopicon"; Description: "创建桌面快捷方式(&D)"; GroupDescription: "附加选项:"; Flags: unchecked
[Files]
Source: "..\build\{#AppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\build\{#DaemonExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\resources\wintun.dll"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"
Name: "{group}\卸载 {#AppName}"; Filename: "{uninstallexe}"
Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Tasks: desktopicon
[Run]
Filename: "{app}\{#AppExeName}"; Description: "立即启动 {#AppName}"; Flags: nowait postinstall skipifsilent
[UninstallDelete]
Type: files; Name: "{app}\wintun.dll"
[Code]
// Kill running LMVPN processes (GUI + daemon) by image name.
// Returns True if at least one process was found and killed.
function KillProcess(const ExeName: String): Boolean;
var
ResultCode: Integer;
begin
Result := False;
// taskkill /IM <name> /F /T — force-kill process tree
if Exec(ExpandConstant('{cmd}'), '/C taskkill /IM ' + ExeName + ' /F /T',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
begin
// exit code 0 = killed, 128 = not found — both are fine
Result := (ResultCode = 0);
end;
end;
// PrepareToInstall runs before file extraction. We kill any running
// LMVPN processes here so that the exes can be overwritten cleanly.
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
KillProcess('{#DaemonExeName}');
KillProcess('{#AppExeName}');
// Give the OS a moment to release file handles
Sleep(800);
Result := '';
end;
// Kill processes before uninstall as well.
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
if CurUninstallStep = usUninstall then
begin
KillProcess('{#DaemonExeName}');
KillProcess('{#AppExeName}');
Sleep(500);
end;
end;
// Check if a process with the given image name is currently running.
function IsProcessRunning(const ExeName: String): Boolean;
var
ResultCode: Integer;
begin
Result := False;
// tasklist exits 0 if the process is found, 1 if not found
if Exec(ExpandConstant('{cmd}'), '/C tasklist /FI "IMAGENAME eq ' + ExeName + '" /NH /FO CSV | findstr /I "' + ExeName + '"',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
Result := (ResultCode = 0);
end;
// Warn the user if LMVPN is still running and offer to kill it
// before proceeding from the directory selection page.
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := True;
if CurPageID = wpSelectDir then
begin
if IsProcessRunning('{#AppExeName}') or IsProcessRunning('{#DaemonExeName}') then
begin
if MsgBox('{#AppName} 正在运行中,需要先关闭才能继续安装。' + #13#10 + #13#10 + '是否立即关闭?',
mbConfirmation, MB_YESNO) = IDYES then
begin
KillProcess('{#DaemonExeName}');
KillProcess('{#AppExeName}');
Sleep(800);
// Verify they're actually dead
if IsProcessRunning('{#AppExeName}') or IsProcessRunning('{#DaemonExeName}') then
begin
MsgBox('无法关闭 {#AppName} 进程,请手动结束任务后重试。', mbError, MB_OK);
Result := False;
end;
end
else
Result := False;
end;
end;
end;