; ────────────────────────────────────────────── ; WGSecure (WGS) — Script Inno Setup ; Installeur Windows complet : WGSecure + WireGuard + ; Visual C++ Redistributable, chacun installé en silence si absent. ; ────────────────────────────────────────────── #define MyAppName "WGSecure" #define MyAppVersion "0.12.1" #define MyAppPublisher "WGSecure" #define MyAppExeName "wgsecure.exe" [Setup] AppId={{8F3B2C1A-7D4E-4A9B-9C6F-1E2D3F4A5B6C} AppName={#MyAppName} AppVersion={#MyAppVersion} AppPublisher={#MyAppPublisher} DefaultDirName={autopf}\{#MyAppName} DefaultGroupName={#MyAppName} DisableProgramGroupPage=yes OutputDir=dist OutputBaseFilename=wgsecure-{#MyAppVersion}-setup Compression=lzma2 SolidCompression=yes ArchitecturesAllowed=x64compatible ArchitecturesInstallIn64BitMode=x64compatible PrivilegesRequired=admin ; Mise à jour d'une installation existante : les deux exécutables à remplacer ; sont en cours d'exécution — wgsecure.exe côté utilisateur, wgsecure-helper.exe ; en service. Windows verrouille l'image d'un processus vivant : sans ce qui ; suit, la copie échoue et l'installeur repart en ayant l'air d'avoir réussi. ; ; Le Restart Manager ferme l'application qui détient les fichiers ; le service, ; lui, est arrêté explicitement avant la copie (cf. StopForUpgrade). Les ; fichiers reçoivent en secours le drapeau `restartreplace`, qui programme le ; remplacement au prochain démarrage si un verrou subsiste malgré tout. CloseApplications=yes CloseApplicationsFilter=*.exe ; Le Restart Manager ferme l'application, il ne la rouvre pas. C'est ; volontaire : lors d'une mise à jour automatique, cet installeur est lancé ; par le service, donc en LocalSystem depuis la session 0 — ce que le Restart ; Manager y relancerait n'aurait ni bureau visible ni les droits de ; l'utilisateur. La relance est confiée au service, qui repart une fois les ; binaires en place et sait ouvrir un processus dans la session console ; (cf. `_relaunch_after_update` dans app/service/helper_service.py). RestartApplications=no [Languages] Name: "french"; MessagesFile: "compiler:Languages\French.isl" [Files] Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion restartreplace ; Service privilégié optionnel (cf. app/service/, `make helper`) : installe/ ; désinstalle le tunnel et pose/retire les règles NRPT (split-DNS) en ; LocalSystem, pour éviter une invite d'authentification à chaque connexion/ ; déconnexion. skipifsourcedoesntexist : le build peut être absent (composant ; optionnel, pas encore généré sur toutes les machines de build) — dans ce ; cas l'app retombe simplement sur son chemin élevé existant. Source: "dist\wgsecure-helper.exe"; DestDir: "{app}"; Flags: ignoreversion restartreplace skipifsourcedoesntexist ; VC++ Redistributable x64 — téléchargé automatiquement par `make installer-deps` : ; https://aka.ms/vs/17/release/vc_redist.x64.exe → installer\VC_redist.x64.exe Source: "installer\VC_redist.x64.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall ; WireGuard pour Windows — prérequis à WGSecure (fournit wg.exe, le pilote ; tunnel et le service WireGuardTunnel$ que wireguard.py pilote). Téléchargé ; automatiquement par `make installer-deps` : ; https://download.wireguard.com/windows-client/wireguard-installer.exe ; → installer\wireguard-installer.exe Source: "installer\wireguard-installer.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall [Icons] Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" [Run] ; Installation silencieuse du VC++ Redistributable si absent (corrige "Failed to load Python DLL: python312.dll") Filename: "{tmp}\VC_redist.x64.exe"; Parameters: "/install /quiet /norestart"; StatusMsg: "Installation du composant Visual C++ Redistributable…"; Check: VCRedistNeedsInstall ; L'installeur officiel WireGuard s'exécute déjà sans assistant (juste une ; fenêtre de progression) : notre propre installeur tourne en admin ; (PrivilegesRequired=admin ci-dessus), donc pas d'UAC supplémentaire non plus. ; `waituntilterminated` : l'app ne doit pas démarrer avant que wg.exe existe. Filename: "{tmp}\wireguard-installer.exe"; StatusMsg: "Installation de WireGuard…"; Check: WireGuardNeedsInstall; Flags: waituntilterminated ; `skipifsilent` est délibéré : cette entrée ne vaut que pour une installation ; lancée à la main, où elle démarre l'application sous le compte qui installe. ; La mise à jour automatique, elle, passe par `/VERYSILENT` sous LocalSystem — ; l'application y serait ouverte dans la session 0, invisible et en SYSTEM. ; C'est le service qui la relance dans la bonne session (cf. RestartApplications). Filename: "{app}\{#MyAppExeName}"; Description: "Lancer {#MyAppName}"; Flags: nowait postinstall skipifsilent [Code] function VCRedistNeedsInstall: Boolean; var Version: String; begin // Clé présente si VC++ 2015-2022 x64 (v14+) déjà installé Result := not RegQueryStringValue(HKLM64, 'SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\X64', 'Version', Version); end; function WireGuardNeedsInstall: Boolean; begin // WireGuard s'installe toujours dans le Program Files 64 bits, quelle que // soit l'architecture de WGSecure : {pf} y pointe bien puisque // ArchitecturesInstallIn64BitMode=x64compatible ci-dessus. Result := not FileExists(ExpandConstant('{pf}\WireGuard\wireguard.exe')); end; // Enregistre et démarre le service privilégié (cf. app/service/) via // Exec()+AddQuotes plutôt que des entrées [Run] à guillemets échappés // ("" imbriqués) : une tentative précédente basée sur l'échappement de // chaîne Inno Setup produisait une commande sc.exe invalide (silencieuse, // aucune erreur visible pendant l'installation) alors que la même commande // fonctionnait telle quelle lancée à la main — AddQuotes() gère le // guillemetage de façon fiable, sans juggler des niveaux d'échappement // imbriqués à la main. procedure InstallHelperService; var ResultCode: Integer; ExePath, Params: String; begin ExePath := ExpandConstant('{app}\wgsecure-helper.exe'); // Optionnel (skipifsourcedoesntexist dans [Files]) : n'enregistrer le // service que si le binaire a bien été copié. if not FileExists(ExePath) then exit; Params := 'create WGSecureHelper binPath= ' + AddQuotes(ExePath) + ' start= auto DisplayName= ' + AddQuotes('WGSecure Helper'); Exec(ExpandConstant('{sys}\sc.exe'), Params, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); Exec(ExpandConstant('{sys}\sc.exe'), 'start WGSecureHelper', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; // sc.exe ignore silencieusement une commande sur un service déjà absent // (ex. installeur généré sans le composant service) : pas besoin de tester // son existence avant d'arrêter/supprimer. procedure UninstallHelperService; var ResultCode: Integer; begin Exec(ExpandConstant('{sys}\sc.exe'), 'stop WGSecureHelper', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); Exec(ExpandConstant('{sys}\sc.exe'), 'delete WGSecureHelper', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; // Le processus du service est-il encore vivant ? // // L'attente précédente demandait à RenameFile() de renommer le binaire sur // lui-même, en tenant l'échec pour un verrou. Elle ne mesurait rien : Windows // laisse renommer l'image d'un processus vivant — c'est même ce qui rend // possible le `restartreplace` — et MoveFile() refuse de toute façon une // destination existante, verrou ou pas. La boucle rendait donc toujours la // même réponse, quel que soit l'état réel du service, et la copie partait sur // un fichier encore tenu : elle échouait, `restartreplace` reportait le // remplacement au redémarrage suivant, et l'installeur s'achevait en // annonçant une réussite que rien n'avait accomplie. // // tasklist interroge la liste des processus, et le nom d'image n'est pas // traduit — contrairement aux libellés d'état de sc.exe, qui le sont sur un // Windows français. function HelperStillRunning: Boolean; var ResultCode: Integer; begin Result := Exec(ExpandConstant('{cmd}'), '/C tasklist /FI "IMAGENAME eq wgsecure-helper.exe" | find /I "wgsecure-helper.exe"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0); end; // Arrêt de tout ce qui tient les fichiers, avant la copie. Le service se // remplace lui-même : il doit donc être à l'arrêt, et `sc stop` rend la main // avant que le processus n'ait fini de mourir — d'où l'attente qui suit. // Les tunnels sont arrêtés au passage : leur service tourne en SYSTEM et // n'est pas remplacé ici, mais laisser le VPN monté pendant que son pilote // disparaît de l'écran n'aurait aucun sens pour l'utilisateur. procedure StopForUpgrade; var ResultCode, I: Integer; Names: TArrayOfString; J: Integer; begin // Tunnels WireGuard posés par WGSecure : le nom d'interface est réglable, // on interroge donc le registre des services plutôt que de deviner « wgs0 ». if RegGetSubkeyNames(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Services', Names) then begin for J := 0 to GetArrayLength(Names) - 1 do if Pos('WireGuardTunnel$', Names[J]) = 1 then Exec(ExpandConstant('{sys}\sc.exe'), 'stop ' + AddQuotes(Names[J]), '', SW_HIDE, ewWaitUntilTerminated, ResultCode); end; Exec(ExpandConstant('{sys}\sc.exe'), 'stop WGSecureHelper', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); // `sc stop` demande l'arrêt sans l'attendre : copier tout de suite // retomberait sur un binaire encore verrouillé. On attend donc la // disparition du *processus*, seule chose qui tienne le verrou — le service // peut être annoncé arrêté alors que son image l'est encore. for I := 1 to 60 do begin if not HelperStillRunning then Break; Sleep(500); end; end; // Signale au service qu'il devra rouvrir l'interface. Uniquement en mode // silencieux : une installation lancée à la main rouvre l'application par son // entrée [Run], sous le compte qui installe, et poser le marqueur ferait // alors deux ouvertures pour une seule installation. // // Écrit ici plutôt que par le service qui a lancé cet installeur : le service // ne sait que l'avoir lancé, cet installeur sait que l'installation a abouti. // Il porte de surcroît déjà la correction, donc la première mise à jour en // profite — un marqueur posé par l'ancien service n'aurait rien produit. // // Avant `InstallHelperService`, qui démarre le service : c'est ce démarrage // qui le relit. procedure MarkRelaunchPending; var Dir: String; begin if not WizardSilent then exit; Dir := ExpandConstant('{commonappdata}\WGSecure'); if not ForceDirectories(Dir) then exit; // pas de marqueur, pas de relance SaveStringToFile(Dir + '\relaunch.pending', '{#MyAppVersion}', False); end; procedure CurStepChanged(CurStep: TSetupStep); begin if CurStep = ssInstall then StopForUpgrade; if CurStep = ssPostInstall then begin MarkRelaunchPending; InstallHelperService; end; end; procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); begin if CurUninstallStep = usUninstall then UninstallHelperService; end;