; 穿山甲 Pangolin Windows 安装包 (Inno Setup 6) ; 编译: ISCC.exe pangolin.iss (或用 Inno Setup Compiler 打开) ; 前置: 先在 client/ 跑 `flutter build windows`(Release),产物在 ; ..\..\build\windows\x64\runner\Release #define MyAppName "穿山甲 Pangolin" #define MyAppVersion "1.0.41" #define MyAppPublisher "Pangolin" #define MyAppExeName "pangolin_vpn.exe" #define BuildDir "..\..\build\windows\x64\runner\Release" [Setup] AppId={{B2F1B0A0-7A3C-4C2E-9E5A-1A2B3C4D5E6F} AppName={#MyAppName} AppVersion={#MyAppVersion} AppPublisher={#MyAppPublisher} DefaultDirName={autopf}\Pangolin DefaultGroupName=Pangolin DisableProgramGroupPage=yes OutputBaseFilename=pangolin-setup-{#MyAppVersion} Compression=lzma2 SolidCompression=yes WizardStyle=modern ; 安装需管理员(写 Program Files;app 本身 requireAdministrator) PrivilegesRequired=admin ArchitecturesInstallIn64BitMode=x64compatible [Languages] ; ChineseSimplified.isl 随仓库附带(此 Inno Setup 安装未含官方简中语言包),与本 .iss 同目录 Name: "chinesesimplified"; MessagesFile: "ChineseSimplified.isl" Name: "english"; MessagesFile: "compiler:Default.isl" [Files] ; 整个 Release 目录(含 pangolin_vpn.exe / flutter_windows.dll / data\ / ; 插件 dll / sing-box.exe / wintun.dll) Source: "{#BuildDir}\*"; DestDir: "{app}"; Flags: recursesubdirs createallsubdirs ignoreversion [Icons] Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" [Run] ; runascurrentuser:安装包是 admin 提权运行的,默认 postinstall 会降回普通用户身份拉起 ; app(Inno 的 RunAsOriginalUser)→ requireAdministrator 的 app 起不来/没管理员权,TUN 建不了。 ; 加此标志用安装包自身的管理员令牌拉起,装完即以管理员运行、不额外弹 UAC。 Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#MyAppName}}"; Flags: nowait postinstall skipifsilent runascurrentuser [UninstallDelete] ; 卸载清理运行期数据(kernel 配置等) Type: filesandordirs; Name: "{localappdata}\Pangolin" [Code] { ── 升级覆盖修复 ────────────────────────────────────────────────────────── 历史坑:1.0.0 的 AppId 是非法 GUID(...PANGOLIN0001),1.0.1 起改成当前合法 GUID。AppId 不同 → Inno 视为两个独立产品 → 旧版不被覆盖,控制面板里并排两个。 当前 AppId 已稳定(1.0.1+ 同源,会原地升级);此处在安装前再把那个遗留 AppId 的残留装静默卸掉,清掉已并存的 1.0.0 幽灵,杜绝多版本并排。} const LegacyAppId = '{B2F1B0A0-7A3C-4C2E-9E5A-PANGOLIN0001}_is1'; function GetUninstallString(const SubKey: string): string; var s: string; begin s := ''; if not RegQueryStringValue(HKLM64, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + SubKey, 'UninstallString', s) then if not RegQueryStringValue(HKLM32, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + SubKey, 'UninstallString', s) then RegQueryStringValue(HKCU, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + SubKey, 'UninstallString', s); Result := s; end; procedure RemoveLegacyVersion; var uninst: string; rc: Integer; begin uninst := GetUninstallString(LegacyAppId); if uninst <> '' then begin uninst := RemoveQuotes(uninst); Exec(uninst, '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART', '', SW_HIDE, ewWaitUntilTerminated, rc); end; end; procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep = ssInstall then RemoveLegacyVersion; end;