Windows
This commit is contained in:
@@ -154,12 +154,17 @@ setup-sudoers:
|
|||||||
@$(PYTHON) -c "\
|
@$(PYTHON) -c "\
|
||||||
import os, subprocess; \
|
import os, subprocess; \
|
||||||
user = os.environ.get('USER', os.path.basename(os.path.expanduser('~'))); \
|
user = os.environ.get('USER', os.path.basename(os.path.expanduser('~'))); \
|
||||||
rule = user + ' ALL=(ALL) NOPASSWD: /usr/bin/wg-quick\n'; \
|
rules = ( \
|
||||||
|
user + ' ALL=(ALL) NOPASSWD: /usr/bin/wg-quick\n' \
|
||||||
|
+ user + ' ALL=(ALL) NOPASSWD: /usr/bin/tee /etc/wireguard/*\n' \
|
||||||
|
+ user + ' ALL=(ALL) NOPASSWD: /bin/chmod 600 /etc/wireguard/*\n' \
|
||||||
|
+ user + ' ALL=(ALL) NOPASSWD: /usr/bin/chmod 600 /etc/wireguard/*\n' \
|
||||||
|
); \
|
||||||
tmp = '/tmp/wgsecure_sudoers_tmp'; \
|
tmp = '/tmp/wgsecure_sudoers_tmp'; \
|
||||||
open(tmp, 'w').write(rule); \
|
open(tmp, 'w').write(rules); \
|
||||||
r = subprocess.run(['pkexec', 'bash', '-c', 'cp ' + tmp + ' /etc/sudoers.d/wgsecure && chmod 440 /etc/sudoers.d/wgsecure']); \
|
r = subprocess.run(['pkexec', 'bash', '-c', 'cp ' + tmp + ' /etc/sudoers.d/wgsecure && chmod 440 /etc/sudoers.d/wgsecure']); \
|
||||||
os.unlink(tmp); \
|
os.unlink(tmp); \
|
||||||
print(' ✅ /etc/sudoers.d/wgsecure configuré — WGSecure n\\'a plus besoin de dialogue admin.' if r.returncode == 0 else ' ❌ Échec. Exécutez manuellement : sudo bash -c \"echo ' + user + ' ALL=(ALL) NOPASSWD: /usr/bin/wg-quick > /etc/sudoers.d/wgsecure && chmod 440 /etc/sudoers.d/wgsecure\"')"
|
print(' ✅ /etc/sudoers.d/wgsecure configuré — plus de dialogue admin.' if r.returncode == 0 else ' ❌ Échec.')"
|
||||||
@echo ""
|
@echo ""
|
||||||
|
|
||||||
# ── Réinitialisation du mot de passe administrateur ──────────────────────────
|
# ── Réinitialisation du mot de passe administrateur ──────────────────────────
|
||||||
|
|||||||
+29
-7
@@ -48,7 +48,9 @@ def build_client_config(cfg: Config) -> str:
|
|||||||
|
|
||||||
def get_client_config_path(cfg: Config) -> str:
|
def get_client_config_path(cfg: Config) -> str:
|
||||||
name = cfg.wg.get("interface_name", "wgs0")
|
name = cfg.wg.get("interface_name", "wgs0")
|
||||||
|
if is_windows():
|
||||||
return os.path.join(get_config_dir(), f"{name}.conf")
|
return os.path.join(get_config_dir(), f"{name}.conf")
|
||||||
|
return os.path.join(get_wg_config_dir(), f"{name}.conf") # /etc/wireguard/
|
||||||
|
|
||||||
|
|
||||||
def write_client_config(cfg: Config) -> tuple[bool, str]:
|
def write_client_config(cfg: Config) -> tuple[bool, str]:
|
||||||
@@ -61,8 +63,30 @@ def write_client_config(cfg: Config) -> tuple[bool, str]:
|
|||||||
if not is_windows():
|
if not is_windows():
|
||||||
os.chmod(path, 0o600)
|
os.chmod(path, 0o600)
|
||||||
return True, path
|
return True, path
|
||||||
except Exception as e:
|
except PermissionError:
|
||||||
return False, str(e)
|
# sudo -n tee (NOPASSWD si configuré, sinon pkexec cp)
|
||||||
|
import subprocess as _sp
|
||||||
|
try:
|
||||||
|
r = _sp.run(["sudo", "-n", "tee", path],
|
||||||
|
input=content, text=True,
|
||||||
|
capture_output=True, timeout=10)
|
||||||
|
if r.returncode == 0:
|
||||||
|
_sp.run(["sudo", "-n", "chmod", "600", path],
|
||||||
|
capture_output=True, timeout=5)
|
||||||
|
return True, path
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Repli sur pkexec cp (dialogue graphique)
|
||||||
|
tmp = os.path.join(get_config_dir(), "wgs_tmp.conf")
|
||||||
|
with open(tmp, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
os.chmod(tmp, 0o600)
|
||||||
|
code, _, err = run_privileged(["cp", tmp, path])
|
||||||
|
os.unlink(tmp)
|
||||||
|
if code == 0:
|
||||||
|
run_privileged(["chmod", "600", path])
|
||||||
|
return True, path
|
||||||
|
return False, err
|
||||||
|
|
||||||
|
|
||||||
def is_connected(cfg: Config) -> bool:
|
def is_connected(cfg: Config) -> bool:
|
||||||
@@ -88,7 +112,8 @@ def connect(cfg: Config) -> tuple[bool, str]:
|
|||||||
code, _, err = run_command(["wireguard", "/installtunnel", result])
|
code, _, err = run_command(["wireguard", "/installtunnel", result])
|
||||||
return (code == 0), (err or "Connecté")
|
return (code == 0), (err or "Connecté")
|
||||||
else:
|
else:
|
||||||
code, _, err = run_privileged(["wg-quick", "up", result])
|
# Passer le nom d'interface (pas le chemin) : AppArmor autorise /etc/wireguard/ seulement
|
||||||
|
code, _, err = run_privileged(["wg-quick", "up", name])
|
||||||
if code == 0:
|
if code == 0:
|
||||||
return True, "Tunnel WireGuard activé"
|
return True, "Tunnel WireGuard activé"
|
||||||
return False, err or "Erreur lors de la connexion"
|
return False, err or "Erreur lors de la connexion"
|
||||||
@@ -101,10 +126,7 @@ def disconnect(cfg: Config) -> tuple[bool, str]:
|
|||||||
code, _, err = run_command(["wireguard", "/uninstalltunnel", name])
|
code, _, err = run_command(["wireguard", "/uninstalltunnel", name])
|
||||||
return (code == 0), (err or "Déconnecté")
|
return (code == 0), (err or "Déconnecté")
|
||||||
else:
|
else:
|
||||||
path = get_client_config_path(cfg)
|
code, _, err = run_privileged(["wg-quick", "down", name])
|
||||||
if not os.path.exists(path):
|
|
||||||
path = name
|
|
||||||
code, _, err = run_privileged(["wg-quick", "down", path])
|
|
||||||
if code == 0:
|
if code == 0:
|
||||||
return True, "Tunnel WireGuard désactivé"
|
return True, "Tunnel WireGuard désactivé"
|
||||||
return False, err or "Erreur lors de la déconnexion"
|
return False, err or "Erreur lors de la déconnexion"
|
||||||
|
|||||||
+79
-54
@@ -132,7 +132,6 @@ class AdminWindow(QDialog):
|
|||||||
tabs.addTab(self._tab_keys(), "Clés")
|
tabs.addTab(self._tab_keys(), "Clés")
|
||||||
tabs.addTab(self._tab_mfa(), "MFA")
|
tabs.addTab(self._tab_mfa(), "MFA")
|
||||||
tabs.addTab(self._tab_test(), "Test connexion")
|
tabs.addTab(self._tab_test(), "Test connexion")
|
||||||
tabs.addTab(self._tab_profiles(), "Profils")
|
|
||||||
tabs.addTab(self._tab_settings(), "Paramètres")
|
tabs.addTab(self._tab_settings(), "Paramètres")
|
||||||
tabs.addTab(self._tab_admin(), "Sécurité")
|
tabs.addTab(self._tab_admin(), "Sécurité")
|
||||||
tabs.addTab(self._tab_about(), "À propos")
|
tabs.addTab(self._tab_about(), "À propos")
|
||||||
@@ -184,19 +183,88 @@ class AdminWindow(QDialog):
|
|||||||
form.addRow(lbl, widget)
|
form.addRow(lbl, widget)
|
||||||
|
|
||||||
def _tab_wireguard(self) -> QWidget:
|
def _tab_wireguard(self) -> QWidget:
|
||||||
w, lay = self._dark_page("🛡️ Configuration WireGuard", "#154360")
|
from PyQt6.QtWidgets import QScrollArea, QListWidget
|
||||||
|
|
||||||
# Barre import / export
|
outer = QWidget()
|
||||||
|
outer.setStyleSheet(_TAB_CSS)
|
||||||
|
outer_lay = QVBoxLayout(outer)
|
||||||
|
outer_lay.setContentsMargins(0, 0, 0, 0)
|
||||||
|
outer_lay.setSpacing(0)
|
||||||
|
|
||||||
|
hdr = QLabel(" 🛡️ WireGuard — Configuration & Profils")
|
||||||
|
hdr.setFixedHeight(36)
|
||||||
|
hdr.setStyleSheet(
|
||||||
|
"background: #154360; color: white; font-size: 13px;"
|
||||||
|
" font-weight: bold; padding-left: 8px;"
|
||||||
|
)
|
||||||
|
outer_lay.addWidget(hdr)
|
||||||
|
|
||||||
|
scroll = QScrollArea()
|
||||||
|
scroll.setWidgetResizable(True)
|
||||||
|
scroll.setStyleSheet(
|
||||||
|
"QScrollArea { border: none; background: #1c2833; }"
|
||||||
|
"QScrollBar:vertical { background: #1c2833; width: 8px; border: none; }"
|
||||||
|
"QScrollBar::handle:vertical { background: #2e4057; border-radius: 4px; }"
|
||||||
|
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; }"
|
||||||
|
)
|
||||||
|
|
||||||
|
content = QWidget()
|
||||||
|
content.setStyleSheet("background: #1c2833;")
|
||||||
|
lay = QVBoxLayout(content)
|
||||||
|
lay.setContentsMargins(14, 12, 14, 12)
|
||||||
|
lay.setSpacing(10)
|
||||||
|
|
||||||
|
# ── Profils ──────────────────────────────────────────────────────
|
||||||
|
grp_p = QGroupBox("Profils")
|
||||||
|
gp = QVBoxLayout(grp_p)
|
||||||
|
gp.setSpacing(6)
|
||||||
|
gp.setContentsMargins(10, 14, 10, 10)
|
||||||
|
|
||||||
|
self._profile_list = QListWidget()
|
||||||
|
self._profile_list.setFixedHeight(72)
|
||||||
|
self._profile_list.setStyleSheet(
|
||||||
|
"QListWidget { background: rgba(255,255,255,0.07); color: white;"
|
||||||
|
" border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; }"
|
||||||
|
"QListWidget::item:selected { background: #2471a3; }"
|
||||||
|
)
|
||||||
|
gp.addWidget(self._profile_list)
|
||||||
|
|
||||||
|
p_btn_row = QHBoxLayout()
|
||||||
|
p_btn_row.setSpacing(6)
|
||||||
|
btn_save_p = QPushButton("💾 Sauvegarder sous…")
|
||||||
|
btn_save_p.setFixedHeight(26)
|
||||||
|
btn_save_p.clicked.connect(self._save_profile)
|
||||||
|
btn_load_p = QPushButton("✅ Charger")
|
||||||
|
btn_load_p.setFixedHeight(26)
|
||||||
|
btn_load_p.clicked.connect(self._load_profile)
|
||||||
|
btn_del_p = QPushButton("🗑️ Supprimer")
|
||||||
|
btn_del_p.setFixedHeight(26)
|
||||||
|
btn_del_p.setStyleSheet(
|
||||||
|
"QPushButton { background: #6e2e1c; color: white; border-radius: 5px;"
|
||||||
|
" padding: 0 12px; border: none; }"
|
||||||
|
"QPushButton:hover { background: #922b21; }"
|
||||||
|
)
|
||||||
|
btn_del_p.clicked.connect(self._delete_profile)
|
||||||
|
p_btn_row.addWidget(btn_save_p)
|
||||||
|
p_btn_row.addWidget(btn_load_p)
|
||||||
|
p_btn_row.addWidget(btn_del_p)
|
||||||
|
gp.addLayout(p_btn_row)
|
||||||
|
lay.addWidget(grp_p)
|
||||||
|
|
||||||
|
# ── Import / Export ──────────────────────────────────────────────
|
||||||
io_row = QHBoxLayout()
|
io_row = QHBoxLayout()
|
||||||
btn_import = QPushButton("📂 Importer un .conf")
|
btn_import = QPushButton("📂 Importer un .conf")
|
||||||
|
btn_import.setFixedHeight(26)
|
||||||
btn_import.clicked.connect(self._import_conf)
|
btn_import.clicked.connect(self._import_conf)
|
||||||
btn_export = QPushButton("💾 Exporter en .conf")
|
btn_export = QPushButton("💾 Exporter en .conf")
|
||||||
|
btn_export.setFixedHeight(26)
|
||||||
btn_export.clicked.connect(self._export_conf)
|
btn_export.clicked.connect(self._export_conf)
|
||||||
io_row.addWidget(btn_import)
|
io_row.addWidget(btn_import)
|
||||||
io_row.addWidget(btn_export)
|
io_row.addWidget(btn_export)
|
||||||
io_row.addStretch()
|
io_row.addStretch()
|
||||||
lay.addLayout(io_row)
|
lay.addLayout(io_row)
|
||||||
|
|
||||||
|
# ── Serveur ──────────────────────────────────────────────────────
|
||||||
grp = QGroupBox("Serveur WireGuard")
|
grp = QGroupBox("Serveur WireGuard")
|
||||||
form = QFormLayout(grp)
|
form = QFormLayout(grp)
|
||||||
form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
|
form.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
|
||||||
@@ -216,6 +284,7 @@ class AdminWindow(QDialog):
|
|||||||
self._row(form, "Clé publique serveur :", self._srv_pubkey)
|
self._row(form, "Clé publique serveur :", self._srv_pubkey)
|
||||||
lay.addWidget(grp)
|
lay.addWidget(grp)
|
||||||
|
|
||||||
|
# ── Interface client ─────────────────────────────────────────────
|
||||||
grp2 = QGroupBox("Interface client")
|
grp2 = QGroupBox("Interface client")
|
||||||
form2 = QFormLayout(grp2)
|
form2 = QFormLayout(grp2)
|
||||||
form2.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
|
form2.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
|
||||||
@@ -233,7 +302,7 @@ class AdminWindow(QDialog):
|
|||||||
self._row(form2, "DNS :", self._dns)
|
self._row(form2, "DNS :", self._dns)
|
||||||
|
|
||||||
self._allowed_ips = QLineEdit()
|
self._allowed_ips = QLineEdit()
|
||||||
self._allowed_ips.setPlaceholderText("0.0.0.0/0")
|
self._allowed_ips.setPlaceholderText("10.8.0.0/24")
|
||||||
self._row(form2, "IPs autorisées :", self._allowed_ips)
|
self._row(form2, "IPs autorisées :", self._allowed_ips)
|
||||||
|
|
||||||
self._keepalive = QSpinBox()
|
self._keepalive = QSpinBox()
|
||||||
@@ -243,7 +312,12 @@ class AdminWindow(QDialog):
|
|||||||
self._row(form2, "Keepalive (s) :", self._keepalive)
|
self._row(form2, "Keepalive (s) :", self._keepalive)
|
||||||
lay.addWidget(grp2)
|
lay.addWidget(grp2)
|
||||||
lay.addStretch()
|
lay.addStretch()
|
||||||
return w
|
|
||||||
|
scroll.setWidget(content)
|
||||||
|
outer_lay.addWidget(scroll)
|
||||||
|
|
||||||
|
self._refresh_profile_list()
|
||||||
|
return outer
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
# ------------------------------------------------------------------ #
|
||||||
# Onglet Clés
|
# Onglet Clés
|
||||||
@@ -401,55 +475,6 @@ class AdminWindow(QDialog):
|
|||||||
lay.addStretch()
|
lay.addStretch()
|
||||||
return w
|
return w
|
||||||
|
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
# Onglet Profils
|
|
||||||
# ------------------------------------------------------------------ #
|
|
||||||
def _tab_profiles(self) -> QWidget:
|
|
||||||
from PyQt6.QtWidgets import QListWidget, QListWidgetItem, QInputDialog
|
|
||||||
w, lay = self._dark_page("👤 Gestion des profils", "#1a4a2a")
|
|
||||||
|
|
||||||
info = QLabel(
|
|
||||||
"Sauvegardez la configuration WireGuard courante sous un nom de profil,\n"
|
|
||||||
"puis basculez entre profils sans passer par la configuration."
|
|
||||||
)
|
|
||||||
info.setWordWrap(True)
|
|
||||||
info.setStyleSheet("color: rgba(255,255,255,0.75); font-size: 12px;")
|
|
||||||
lay.addWidget(info)
|
|
||||||
|
|
||||||
grp = QGroupBox("Profils sauvegardés")
|
|
||||||
gv = QVBoxLayout(grp)
|
|
||||||
|
|
||||||
self._profile_list = QListWidget()
|
|
||||||
self._profile_list.setFixedHeight(130)
|
|
||||||
self._profile_list.setStyleSheet(
|
|
||||||
"QListWidget { background: rgba(255,255,255,0.07); color: white;"
|
|
||||||
" border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; }"
|
|
||||||
"QListWidget::item:selected { background: #2471a3; }"
|
|
||||||
)
|
|
||||||
gv.addWidget(self._profile_list)
|
|
||||||
|
|
||||||
btn_row = QHBoxLayout()
|
|
||||||
btn_save_p = QPushButton("💾 Sauvegarder sous…")
|
|
||||||
btn_save_p.clicked.connect(self._save_profile)
|
|
||||||
btn_load_p = QPushButton("✅ Charger")
|
|
||||||
btn_load_p.clicked.connect(self._load_profile)
|
|
||||||
btn_del_p = QPushButton("🗑️ Supprimer")
|
|
||||||
btn_del_p.setStyleSheet(
|
|
||||||
"QPushButton { background: #6e2e1c; color: white; border-radius: 5px;"
|
|
||||||
" padding: 7px 12px; border: none; }"
|
|
||||||
"QPushButton:hover { background: #922b21; }"
|
|
||||||
)
|
|
||||||
btn_del_p.clicked.connect(self._delete_profile)
|
|
||||||
btn_row.addWidget(btn_save_p)
|
|
||||||
btn_row.addWidget(btn_load_p)
|
|
||||||
btn_row.addWidget(btn_del_p)
|
|
||||||
gv.addLayout(btn_row)
|
|
||||||
lay.addWidget(grp)
|
|
||||||
lay.addStretch()
|
|
||||||
|
|
||||||
self._refresh_profile_list()
|
|
||||||
return w
|
|
||||||
|
|
||||||
def _refresh_profile_list(self):
|
def _refresh_profile_list(self):
|
||||||
self._profile_list.clear()
|
self._profile_list.clear()
|
||||||
active = self._cfg.active_profile
|
active = self._cfg.active_profile
|
||||||
|
|||||||
+17
-14
@@ -62,9 +62,13 @@ def run_privileged(cmd: list[str], timeout: int = 60) -> tuple[int, str, str]:
|
|||||||
if has_root_privileges():
|
if has_root_privileges():
|
||||||
return run_command(cmd, timeout)
|
return run_command(cmd, timeout)
|
||||||
|
|
||||||
# 1. pkexec — dialogue graphique polkit (GNOME/KDE), timeout long pour la saisie
|
# 1. sudo -n (NOPASSWD configuré dans sudoers — sans aucun dialogue)
|
||||||
code, out, err = run_command(["pkexec"] + cmd, timeout)
|
code, out, err = run_command(["sudo", "-n"] + cmd, timeout)
|
||||||
if code != -1 or "introuvable" not in err:
|
if code == 0:
|
||||||
|
return code, out, err
|
||||||
|
# Si sudo a tourné mais la commande a échoué (pas un problème d'auth), retourner l'erreur
|
||||||
|
if code != 1 or ("password" not in err.lower() and "passwd" not in err.lower()):
|
||||||
|
if "sudo:" not in err.lower() and code not in (-1,):
|
||||||
return code, out, err
|
return code, out, err
|
||||||
|
|
||||||
# 2. sudo avec programme askpass graphique (pas de TTY dans une app Qt)
|
# 2. sudo avec programme askpass graphique (pas de TTY dans une app Qt)
|
||||||
@@ -78,24 +82,23 @@ def run_privileged(cmd: list[str], timeout: int = 60) -> tuple[int, str, str]:
|
|||||||
capture_output=True, text=True,
|
capture_output=True, text=True,
|
||||||
timeout=timeout, env=env,
|
timeout=timeout, env=env,
|
||||||
)
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
return result.returncode, result.stdout.strip(), result.stderr.strip()
|
return result.returncode, result.stdout.strip(), result.stderr.strip()
|
||||||
except subprocess.TimeoutExpired:
|
except (subprocess.TimeoutExpired, Exception):
|
||||||
return -1, "", "Timeout élévation sudo"
|
pass
|
||||||
except Exception as e:
|
|
||||||
return -1, "", str(e)
|
|
||||||
|
|
||||||
# 3. sudo classique (fonctionne si NOPASSWD configuré dans sudoers)
|
# 3. pkexec — dialogue graphique polkit (GNOME/KDE)
|
||||||
code, out, err = run_command(["sudo", "-n"] + cmd, 10)
|
code, out, err = run_command(["pkexec"] + cmd, timeout)
|
||||||
if code != -1:
|
if code != -1 or "introuvable" not in err:
|
||||||
return code, out, err
|
return code, out, err
|
||||||
|
|
||||||
return (
|
return (
|
||||||
-1, "",
|
-1, "",
|
||||||
"Élévation de privilèges impossible.\n"
|
"Élévation de privilèges impossible.\n"
|
||||||
"Solutions :\n"
|
"Exécutez make setup-sudoers pour configurer wg-quick sans dialogue.\n"
|
||||||
" • Installer pkexec (polkit) pour le dialogue graphique\n"
|
"Ou manuellement :\n"
|
||||||
" • Ou ajouter dans /etc/sudoers :\n"
|
f" sudo bash -c \"echo '{os.environ.get('USER','<user>')} ALL=(ALL) NOPASSWD: /usr/bin/wg-quick'"
|
||||||
f" {os.environ.get('USER','<user>')} ALL=(ALL) NOPASSWD: /usr/bin/wg-quick"
|
" > /etc/sudoers.d/wgsecure && chmod 440 /etc/sudoers.d/wgsecure\""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
; ──────────────────────────────────────────────
|
||||||
|
; WGSecure (WGS) — Script Inno Setup
|
||||||
|
; Installeur Windows (bundle VC++ Redistributable)
|
||||||
|
; ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
#define MyAppName "WGSecure"
|
||||||
|
#define MyAppVersion "0.4.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
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
; VC++ Redistributable x64 — à télécharger avant compilation :
|
||||||
|
; 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
|
||||||
|
|
||||||
|
[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
|
||||||
|
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;
|
||||||
Reference in New Issue
Block a user