248 lines
11 KiB
Plaintext
248 lines
11 KiB
Plaintext
; ──────────────────────────────────────────────
|
|
; 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.13.0"
|
|
#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;
|