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é.
This commit is contained in:
2026-09-01 19:18:24 +02:00
parent 6251a3ea8e
commit 8c09c02362
13 changed files with 590 additions and 104 deletions
+9 -6
View File
@@ -14,6 +14,7 @@ from app.utils.platform_utils import (
is_windows,
run_command,
run_privileged,
run_privileged_readonly,
wg_available,
wg_exe,
wg_quick_available,
@@ -150,12 +151,14 @@ def write_client_config(cfg: Config) -> tuple[bool, str]:
def _wg_show(name: str) -> str:
"""Sortie de `wg show <iface>`, ou "" si les privilèges manquent.
Attention : sans CAP_NET_ADMIN, `wg show` écrit « Unable to access
interface: Operation not permitted » sur stderr **et sort avec le code 0**.
Un simple test `code == 0` conclut donc « déconnecté » pour tout
utilisateur non root — c'était le cas de l'application entière.
`wg show` exige CAP_NET_ADMIN (root) pour lire l'état d'une interface,
même en lecture seule — un utilisateur ordinaire reçoit « Unable to
access interface: Operation not permitted ». D'où l'appel via
`run_privileged_readonly` : sans lui, le handshake et les compteurs de
trafic restent vides dès que l'application ne tourne plus en root (le cas
normal une fois `make setup-sudoers` en place pour wg-quick).
"""
code, out, err = run_command([wg_exe(), "show", name])
code, out, err = run_privileged_readonly([wg_exe(), "show", name])
if code != 0 or not out:
return ""
if "not permitted" in err.lower() or "unable to access" in err.lower():
@@ -402,7 +405,7 @@ def last_handshake_age(name: str) -> int | None:
Retourne -1 si aucun handshake n'a jamais eu lieu, None si l'information
est inaccessible (privilèges insuffisants, `wg` absent).
"""
code, out, _ = run_command([wg_exe(), "show", name, "latest-handshakes"], 10)
code, out, _ = run_privileged_readonly([wg_exe(), "show", name, "latest-handshakes"], 10)
if code != 0 or not out.strip():
return None
newest = 0