Files
WGSecure/main.py
T
tuxgyver 8c09c02362 v0.7.0 : instance unique, handshake ré-affiché, sudoers fiabilisé, À propos refaite
- Instance unique : un second lancement réveille l'instance active.
- Autostart et reconnexion auto activés par défaut (nouvelle installation).
- Page « À propos » refaite en 5 onglets (contenu séparé dans app_info.py).
- Corrige le handshake/RX-TX qui ne s'affichaient plus (wg show sans élévation).
- Corrige le diagnostic sudo trompeur (sondes qui ne matchaient jamais les règles).
- Corrige make setup-sudoers bloqué hors session graphique (pkexec -> sudo), idempotent.
- Corrige le README : Kill Switch inexistant retiré, ordre d'élévation réel documenté.
2026-09-01 19:18:24 +02:00

174 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""
WGSecure (WGS) — Interface graphique WireGuard avec MFA
"""
import sys
import argparse
from PyQt6.QtWidgets import QApplication, QMessageBox, QInputDialog, QLineEdit, QSystemTrayIcon
from PyQt6.QtCore import Qt
from app import __version__
from app.core.config import Config
from app.core import dns as dns_util
from app.core import log as conn_log
from app.core import shutdown as shutdown_guard
from app.core.single_instance import SingleInstanceGuard
from app.ui.main_window import MainWindow
from app.ui.systray import SystemTray
from app.ui import icons
from app.utils.platform_utils import install_desktop_entry
def _install_taskbar_icon(app_icon) -> None:
"""Exporte l'icône en PNG 256 px et installe l'entrée .desktop."""
try:
import tempfile, os
tmp = os.path.join(tempfile.gettempdir(), "wgsecure_icon.png")
app_icon.pixmap(256, 256).save(tmp, "PNG")
install_desktop_entry(tmp)
except Exception:
pass
def recover_dns_state(config: Config) -> None:
"""Répare la résolution DNS laissée détournée par une session précédente.
Si l'application (ou la machine) s'est arrêtée sans exécuter
`wg-quick down`, la configuration DNS du tunnel reste active alors que le
résolveur correspondant est injoignable : plus aucun nom ne se résout.
Le nettoyage n'a lieu que si un résidu est effectivement constaté.
"""
iface = config.wg.get("interface_name", "wgs0")
try:
repaired, msg = dns_util.repair_if_broken(iface, config.wg.get("dns", ""))
except Exception as e: # noqa: BLE001
repaired, msg = False, f"Vérification DNS impossible : {e}"
if msg:
conn_log.log_event(msg, "warning" if repaired else "error")
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="wgsecure",
description="WGSecure — WireGuard GUI avec MFA",
)
parser.add_argument(
"--admin",
action="store_true",
help="Lancer directement en mode Administrateur",
)
parser.add_argument(
"--no-tray",
action="store_true",
help="Désactiver l'icône systray",
)
return parser.parse_args()
def require_admin_auth(config: Config, app: QApplication) -> bool:
"""Vérifie le mot de passe admin si configuré. Retourne True si autorisé."""
if not config.has_admin_password():
return True
pw, ok = QInputDialog.getText(
None,
"Accès Administrateur",
"Mot de passe administrateur :",
QLineEdit.EchoMode.Password,
)
if not ok:
return False
if config.check_admin_password(pw):
return True
QMessageBox.warning(None, "WGSecure", "Mot de passe incorrect.")
return False
def main():
args = parse_args()
# Support DPI haute résolution
QApplication.setHighDpiScaleFactorRoundingPolicy(
Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
)
app = QApplication(sys.argv)
app.setApplicationName("WGSecure")
app.setApplicationVersion(__version__)
app.setOrganizationName("WGS")
# Une seule instance à la fois : un second lancement réveille la
# première plutôt que d'ouvrir une deuxième fenêtre sur le même tunnel.
guard = SingleInstanceGuard()
if not guard.try_acquire():
sys.exit(0)
# Icône globale multi-tailles — barre des tâches + alt-tab
app_icon = icons.icon_app()
app.setWindowIcon(app_icon)
app.setDesktopFileName("wgsecure")
# Installe .desktop + PNG pour que GNOME/KDE affiche l'icône sur la taskbar
_install_taskbar_icon(app_icon)
# Empêche la fermeture quand toutes les fenêtres sont cachées (tray)
app.setQuitOnLastWindowClosed(False)
config = Config()
# Reste d'une session précédente mal terminée : DNS détourné vers un
# résolveur mort. À faire avant d'ouvrir la fenêtre pour rendre l'accès
# au réseau tout de suite.
recover_dns_state(config)
# Restauration de l'état réseau garantie sur toutes les sorties :
# Quitter, croix, SIGINT/SIGTERM, atexit, exception non rattrapée.
shutdown_guard.install(config, report=conn_log.log_event, app=app)
# Mode Admin direct (--admin)
if args.admin:
if not require_admin_auth(config, app):
sys.exit(0)
from app.ui.admin_window import AdminWindow
win = AdminWindow(config)
win.setWindowIcon(icons.icon_admin())
win.show()
guard.on_activate_requested(lambda: _raise_window(win))
sys.exit(app.exec())
# Mode User (défaut)
window = MainWindow(config)
guard.on_activate_requested(lambda: _raise_window(window))
# Systray
if not args.no_tray and QSystemTrayIcon_available():
tray = SystemTray(config, window)
tray.show()
# Démarrer réduit si déjà configuré
if config.configured:
window.show()
else:
window.show()
tray.showMessage(
"WGSecure",
"Bienvenue ! Ouvrez le panneau Administrateur pour configurer WireGuard.",
QSystemTrayIcon.MessageIcon.Information,
4000,
)
else:
window.show()
sys.exit(app.exec())
def _raise_window(window) -> None:
"""Ramène une fenêtre existante au premier plan (second lancement)."""
window.showNormal()
window.raise_()
window.activateWindow()
def QSystemTrayIcon_available() -> bool:
from PyQt6.QtWidgets import QSystemTrayIcon
return QSystemTrayIcon.isSystemTrayAvailable()
if __name__ == "__main__":
main()