"""Contenu de la page « À propos ». Source de vérité unique pour les textes de la page : l'onglet Qt (app/ui/admin_window.py, méthode `_tab_about`) ne fait que les mettre en forme. La version, elle, vient de `app.__version__` (app/__init__.py), le seul endroit où elle est bumpée à chaque release — jamais recopiée en dur ici. """ import os import sys from app import __version__ # En développement, CHANGELOG.md vit à la racine du dépôt, deux niveaux # au-dessus de ce fichier. Une fois empaqueté par PyInstaller (--onefile), # `__file__` pointe vers l'intérieur de l'exécutable et ce chemin n'existe # plus : le fichier doit alors être cherché dans `sys._MEIPASS`, le dossier # temporaire où l'exe extrait les données embarquées via `--add-data`. if getattr(sys, "frozen", False): _CHANGELOG_PATH = os.path.join(getattr(sys, "_MEIPASS", ""), "CHANGELOG.md") else: _CHANGELOG_PATH = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "CHANGELOG.md" ) APP_NAME = "WGSecure" TAGLINE = "Client WireGuard avec surcouche MFA TOTP, pour Linux et Windows" VERSION = __version__ LICENSE = "Usage personnel" REPO_URL = "https://git.tuxtech.fr/tuxgyver/WGSecure" AUTHOR_NAME = "Johnny" AUTHOR_ROLE = "Auteur" FEATURES = [ ( "Connexion en un clic", "Icône dans la barre système, montage/démontage du tunnel WireGuard " "sans quitter le systray.", ), ( "Suivi en direct", "Statut RX/TX, dernier handshake et latence affichés en continu ; " "graphique de bande passante sur les 30 dernières secondes.", ), ( "Journal et historique", "Horodatage de chaque connexion, déconnexion et erreur ; historique " "des sessions passées (durée, volume transféré, serveur).", ), ( "Multi-profils", "Plusieurs configurations WireGuard sauvegardées et basculables, " "avec import/export de fichiers .conf standards. Les champs issus " "d'un import se verrouillent pour éviter une divergence silencieuse " "avec le pair déjà configuré côté serveur.", ), ( "Génération de clés", "Paire de clés Curve25519 générée nativement en Python, sans " "dépendre du binaire `wg` pour cette étape.", ), ( "Mode Admin protégé", "Configuration complète (serveur, DNS, IP, keepalive, MTU) derrière " "un mot de passe et, en option, une double authentification TOTP.", ), ( "Diagnostic réseau", "Test de handshake réel sans monter le tunnel, détection du " "filtrage sur port source, et test de fuite DNS.", ), ( "Auto-reconnexion", "Surveillance du tunnel et reconnexion automatique en cas de " "coupure, à l'intervalle choisi.", ), ( "Split-DNS", "Configuration d'un serveur DNS dédié pour les noms du réseau distant " "(ex: 192.168.1.210 pour .local), tout en utilisant le DNS système " "pour le reste du trafic internet. Le test de connexion vérifie que " "les domaines configurés s'y résolvent réellement, et un outil de " "résolution à la demande permet de tester n'importe quel nom précis.", ), ] GUARANTEES_INTRO = "Ce que l'application garantit, quel que soit le profil configuré :" GUARANTEES = [ "Le mot de passe du mode Admin n'est jamais stocké en clair : il est " "haché avec bcrypt (12 tours) avant d'être écrit dans la configuration.", "La clé privée WireGuard est toujours écrite avec des permissions " "restrictives (600) — aussi bien dans la configuration locale que dans " "le fichier installé sous /etc/wireguard.", "Le test de handshake et le sondage de statut (dernier handshake, " "RX/TX) n'ouvrent jamais de tunnel et ne modifient aucun fichier : ce " "sont des lectures ou un simple datagramme UDP jetable.", "Les commandes système nécessitant les privilèges root sont exécutées " "une par une, jamais l'application entière : `make setup-sudoers` " "autorise sans mot de passe uniquement wg-quick, wg, et la suppression " "d'interface/DNS orpheline — jamais un accès root généralisé.", "Le sondage périodique de l'état du tunnel (toutes les 3 secondes) " "n'ouvre jamais de dialogue de mot de passe ou d'UAC : il échoue " "silencieusement si les privilèges nécessaires ne sont pas déjà " "accordés.", "Le test de fuite DNS vérifie que les requêtes sortent bien par le " "resolver attendu une fois le tunnel monté, plutôt que de supposer la " "configuration correcte.", ] TECH_STACK = [ ("Interface", "PyQt6"), ("Cryptographie", "cryptography (X25519, ChaCha20-Poly1305, bcrypt)"), ("MFA", "pyotp (TOTP) + qrcode/Pillow pour le QR code"), ("Tunnel", "wg-quick / wg (Linux), WireGuard for Windows"), ("Élévation de privilèges", "sudo NOPASSWD ciblé, repli askpass puis pkexec"), ("Empaquetage", "PyInstaller (Linux/Windows) + Inno Setup (installeur Windows)"), ] def parse_changelog() -> list[dict]: """Lit CHANGELOG.md : titres `## [x.y.z] — date`, sous-titres `### ...` et puces `- ...` (avec leurs lignes de continuation) uniquement — le format effectivement utilisé par ce fichier, sans dépendance Markdown. """ entries: list[dict] = [] version = None section = None try: with open(_CHANGELOG_PATH, encoding="utf-8") as f: for raw in f: line = raw.rstrip("\n") if line.startswith("## "): version = {"title": line[3:].strip(), "sections": []} entries.append(version) section = None elif line.startswith("### ") and version is not None: section = {"name": line[4:].strip(), "items": []} version["sections"].append(section) elif line.startswith("- ") and section is not None: section["items"].append(line[2:].strip()) elif (line.strip() and section is not None and section["items"] and not line.startswith(("#", "---"))): # Ligne de continuation d'une puce étalée sur plusieurs lignes. section["items"][-1] += " " + line.strip() except FileNotFoundError: pass return entries