Commit Graph
79 Commits
Author SHA1 Message Date
tuxgyverandClaude Opus 5 142eff5916 feat(ui): un thème clair, et plus une couleur hors du module qui les tient
Le module de thème publie deux palettes derrière les mêmes noms : `apply()`
rebranche `BG`, `TEXT`, `TAB_CSS` sur celle qui est choisie au lancement, et
les cent soixante-dix appels répartis dans l'interface continuent de lire
`theme.BG` sans rien savoir du thème actif. Aucun widget n'a eu à changer —
c'est ce que permettait le passage systématique par `theme.X` à l'usage.

Le choix est lu une seule fois, au démarrage. Les feuilles de style sont
posées dans les constructeurs des fenêtres ; les repeindre à chaud
supposerait de reconstruire celle qui porte l'état du tunnel, ses fils
d'exécution et ses minuteries. Le message de confirmation annonce donc la
prise d'effet au prochain lancement plutôt que de laisser croire le contraire.
Le thème sombre reste le défaut, et rend exactement les mêmes feuilles de
style qu'avant.

Un thème clair n'est pas une inversion, et trois familles de valeurs vivaient
en dur là où elles étaient jusqu'ici invisibles. Dans la feuille de style
commune, les variantes d'état des champs et des boutons étaient des voiles
blancs translucides — c'est-à-dire rien du tout sur un fond clair. Le texte
posé sur un aplat saturé — bouton plein, onglet actif, bandeau, sélection —
portait `TEXT`, qui devient presque noir en thème clair et disparaîtrait sur
l'accent : un jeton distinct le dit, blanc dans les deux thèmes. Enfin le
graphe de bande passante peignait son fond en dur, soit un panneau sombre au
milieu d'une page claire ; ses sept teintes sont devenues des jetons, comme
quatre couleurs de survol de la fenêtre principale.

Deux endroits figeaient par ailleurs le thème au chargement de leur module et
auraient gardé les teintes sombres quel que soit le réglage : la feuille de
style de la fenêtre d'historique, bâtie au niveau du module, et la table
état → couleur du diagnostic, évaluée comme attribut de classe. La première
devient une fonction, la seconde ne retient que la puce et demande la couleur
au thème au moment de l'affichage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 20:01:12 +02:00
tuxgyverandClaude Opus 5 0bb8036df8 chore(release): v0.12.1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v0.12.1
2026-09-08 18:14:00 +02:00
tuxgyverandClaude Opus 5 e781acbf4a fix(update): sous Windows, l'application est relancée et la copie attend vraiment
Rien ne rouvrait l'application après une mise à jour. Trois choses y
concouraient : le service lance l'installeur avec `/VERYSILENT`, l'entrée
`[Run]` qui la rouvre porte `skipifsilent` et s'en trouve écartée, et
`RestartApplications=no` laisse le Restart Manager fermer sans rouvrir. Le
service annonçait pourtant qu'elle redémarrerait.

Lui rendre l'entrée `[Run]` n'aurait rien réglé : l'installeur est lancé par
le service, donc en LocalSystem dans la session 0, où l'application se serait
ouverte sans bureau visible et avec les privilèges du système. La relance
revient au service, seul composant capable d'ouvrir un processus dans la
session d'un autre, et que l'installeur redémarre une fois les binaires en
place. Le jeton vient de la session console : l'application repart avec les
droits de l'utilisateur, pas ceux du service.

Le marqueur qui la commande est posé par l'installeur et non par le service :
celui-ci ne saurait qu'avoir lancé un installeur, quand l'installeur sait que
l'installation a abouti. Il porte de surcroît déjà cette correction, si bien
que la première mise à jour en bénéficie — un marqueur écrit par l'ancien
service ne serait apparu qu'à la deuxième. Posé en installation silencieuse
seulement, l'entrée `[Run]` couvrant déjà l'installation manuelle.

L'attente qui devait laisser mourir le service avant la copie ne mesurait par
ailleurs rien : elle demandait à `RenameFile` de renommer le binaire sur
lui-même et tenait l'échec pour un verrou, alors que Windows autorise le
renommage de l'image d'un processus vivant — c'est ce qui rend
`restartreplace` possible — et que `MoveFile` refuse de toute façon une
destination existante. La copie partait donc sur un fichier encore tenu,
échouait, `restartreplace` reportait le remplacement au redémarrage suivant,
et l'installeur s'achevait en annonçant une réussite. L'attente porte
désormais sur la disparition du processus, relevée par `tasklist` — dont le
nom d'image, contrairement aux libellés d'état de `sc.exe`, n'est pas traduit
sur un Windows français.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 18:14:00 +02:00
tuxgyverandClaude Opus 5 99e1db5880 chore(release): v0.12.0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
v0.12.0
2026-09-08 17:13:35 +02:00
tuxgyverandClaude Opus 5 9e72fa4155 feat(ui): une saisie réussie se voit, et le trafic est vérifié derrière
Un code accepté ne se manifestait que par une ligne de journal, écrite
pendant que la fenêtre de saisie se refermait : rien à l'écran ne distinguait
une validation d'un abandon, et l'échéance obtenue — ce que l'utilisateur
venait précisément de demander — restait invisible. Une confirmation
l'annonce désormais, avec sa date de fin et le temps restant.

Le serveur peut par ailleurs enregistrer l'autorisation sans que le trafic
passe pour autant : la règle de pare-feu peut ne pas s'appliquer, et le
réseau reste alors muet malgré un code accepté. Le client le constate
maintenant en fond, en observant ce qui passe là où le sondage se contente de
demander au serveur ce qu'il a enregistré. `remote_network_reachable` devient
publique pour cela — l'interface l'appelle, et un nom privé traversant la
frontière du module aurait menti sur sa portée.

Corrige au passage un refus qui, au lieu de s'afficher, interrompait le
traitement de la réponse. Contre un serveur antérieur à la v0.3.2, qui exige
encore l'identifiant que ce poste ne transmet plus, `POST /api/session` répond
422 ; le `detail` d'une erreur de validation FastAPI est une liste, non une
phrase, et elle atterrissait telle quelle dans un champ que l'interface passe
ensuite à Qt. Les deux gestionnaires `HTTPError` extrayaient ce champ par les
mêmes quatre lignes : elles deviennent `_error_detail`, qui ne retient qu'une
chaîne et rattrape en plus le corps JSON sans objet, sur lequel `.get` levait
une `AttributeError` non capturée. Le 422 dit désormais quoi faire — mettre le
serveur à jour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 17:13:35 +02:00
tuxgyver 2f2da257c3 chore(release): v0.11.0 v0.11.0 2026-09-08 14:37:11 +02:00
tuxgyverandClaude Opus 5 e520e30716 feat(update): mise à jour servie par le serveur, depuis l'intérieur du tunnel
Les binaires ne peuvent pas venir du dépôt Gitea : il est privé, et un jeton
embarqué dans une application distribuée est un jeton donné à tous ses
utilisateurs. Ils viennent donc du serveur WGSecure, où le contrôle d'accès
existe déjà — l'API n'est joignable que par le tunnel, et on y parvient parce
que sa clé WireGuard est déclarée. Aucun secret à embarquer.

Le serveur expose GET /api/version, qui rend la version publiée et l'empreinte
de chaque artefact, et /api/version/download/{clé} qui les sert. Le manifeste
est déposé par `make publish-updates`, avec les condensats calculés une fois à
la publication : les recalculer à chaque appel bloquerait l'API sur 125 Mo.
`ANNOUNCE=` permet d'annoncer les artefacts sous un autre numéro, pour exercer
la chaîne sans compiler une seconde version.

Rien ne s'installe sans un clic. La vérification est automatique — au montage
du tunnel puis toutes les demi-heures, car ne la faire qu'au montage laissait
un serveur momentanément injoignable annuler toute proposition pour la session
— mais le téléchargement attend le bouton. Cette application monte un VPN, et
une version défaillante qui se propagerait seule couperait l'accès d'un parc
entier sans que personne ne l'ait demandé.

L'artefact est vérifié contre son empreinte SHA-256 avant d'être mis en place,
et le fichier temporaire est détruit à la moindre anomalie. Le canal est déjà
authentifié par WireGuard ; le condensat couvre ce qu'il ne couvre pas — un
téléchargement tronqué, un disque plein, un artefact mal publié.

Sous Linux, le binaire est remplacé par renommage, sur le même système de
fichiers que sa destination. Un déplacement depuis /tmp se rabattait sur une
copie, donc sur une écriture dans l'exécutable en cours, que le noyau refuse
(ETXTBSY). Un renommage ne touche qu'une entrée de répertoire : l'ancien inode
reste vivant pour le processus, qui continue jusqu'à sa fermeture — et
l'application propose désormais de redémarrer plutôt que de le laisser deviner.

Sous Windows, l'installation passe par le service, qui tourne en LocalSystem et
écrit dans Program Files sans invite d'élévation. La commande `apply_update` ne
prend aucun paramètre : le service lit le manifeste, télécharge et vérifie
lui-même. Lui passer un fichier déjà téléchargé aurait donné à tout compte du
poste — le pipe est ouvert aux utilisateurs interactifs — le moyen de faire
exécuter ce qu'il veut avec les privilèges du système.

L'installeur orchestre le remplacement, ce qu'il ne faisait pas : les deux
exécutables à remplacer tournent au moment de la mise à jour, et Windows
verrouille l'image d'un processus vivant. Il ferme donc l'application par le
gestionnaire de redémarrage, arrête les tunnels et le service avant la copie —
en attendant la libération réelle du fichier, `sc stop` rendant la main avant
la fin de l'arrêt — et garde `restartreplace` en secours. Le service, lui,
lance l'installeur détaché et ne l'attend pas : il lui demande de se remplacer
lui-même.

Durcissement du service au passage. Le premier appelant devient propriétaire et
son SID est retenu ; ensuite seuls ce compte et les administrateurs sont
servis. Le pipe étant ouvert à tout utilisateur interactif, n'importe quel
compte du poste pouvait jusqu'ici couper le tunnel d'un autre ou poser une
règle NRPT valable pour toute la machine. Le contenu d'un `.conf` est également
vérifié — seul son chemin l'était — et les directives exécutables y sont
refusées.

Corrige enfin un défaut de compilation : Linux et Windows partageaient le
répertoire de travail de PyInstaller, que `--clean` vide au démarrage. Lancées
à la suite, les deux cibles effaçaient mutuellement leurs fichiers
intermédiaires, produisant un .exe gonflé de 44 Mo et un binaire Linux tronqué
dont l'archive ne se décompressait plus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:33:52 +02:00
tuxgyver 414144560e chore(release): v0.10.0 v0.10.0 2026-09-08 12:20:19 +02:00
tuxgyverandClaude Opus 5 b3cf706bb6 feat(ui): panneau allégé, et le diagnostic nomme le chevauchement de plages
L'assistant « configuration serveur + client » disparaît. Il datait d'avant la
v0.8.0 et produisait un serveur WireGuard nu — sans API, sans quarantaine, sans
TOTP — incompatible avec le produit actuel, où le serveur enrôle le compte et
fournit un .conf. Son bouton « Appliquer le volet client » écrasait clés client
et clé publique du serveur : sur un poste déjà enrôlé, il détruisait le lien
avec le serveur, qui déclare l'ancienne clé publique.

L'identifiant VPN disparaît aussi, devenu sans emploi depuis que le serveur
reconnaît le poste à l'adresse de son tunnel. Un champ qui ne fait rien est
pire qu'un champ absent : il laisse croire qu'il compte. Les configurations
existantes sont purgées au chargement, profils compris. La pastille « À propos »
qui en déduisait « Code d'accès serveur » ou « Aucun code d'accès » n'affirme
plus rien qu'elle ne sache : l'accès passe par le serveur dans tous les cas.

Le sous-onglet Tunnel passe d'une liste de quinze champs à trois groupes :
l'essentiel, le split-DNS nommé comme tel — trois champs y portaient « DNS »
sans que rien ne dise lesquels allaient ensemble — et un repli « Avancé ».
Celui-ci réunit ce qui vient du serveur (adresse, port, clé publique) et ce qui
est correct par défaut (nom d'interface, URL de l'API, keepalive, MTU).

Le verrouillage après import d'un .conf devient persistant. Il existait déjà,
mais `_load_values` le levait sans condition : rouvrir le panneau ou changer de
profil rendait éditables, en silence, les champs décrivant un pair déjà déclaré
côté serveur. L'état est désormais enregistré et suit la configuration chargée.

Le diagnostic gagne une étape « Serveur DNS du réseau distant », qui couvre un
angle mort : le tunnel monte, le trafic passe, la résolution est au vert, et
pas un nom interne ne se résout. Deux causes, l'une et l'autre muettes — le
serveur DNS distant n'est couvert par aucune IP autorisée, ou il l'est mais son
adresse tombe aussi dans une plage que porte une carte du poste, auquel cas la
route locale l'emporte. Le second cas ne se déduit pas de la configuration :
l'étape interroge la route réellement retenue par le noyau, sans émettre de
paquet, et propose la route d'hôte qui reprend la main.

Le sous-onglet « Fuite DNS » devient « Résolution des noms » : il en faisait
trois choses sous un titre qui n'en annonçait qu'une. Son verdict « Statut
inconnu » recouvrait trois causes appelant trois gestes différents ; chacune
est maintenant nommée, avec l'action qui suit.

Enfin, la fenêtre. Sa hauteur augmente de 15 %, et son plancher passe de 520 à
703 : la page de test réclame 569 px et se chevauchait en dessous. Le rapport
de diagnostic passe en tableau à deux colonnes — il occupait 224 px quelle que
soit la largeur de la fenêtre, soit jusqu'à 79 % de blanc à droite, et tient
maintenant sur 38 % de hauteur en moins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 12:18:08 +02:00
tuxgyver dd899fadc6 chore(release): v0.9.1 v0.9.1 2026-09-08 11:22:30 +02:00
tuxgyverandClaude Opus 5 023ec81e03 fix(auth): le serveur reconnaît le poste à son adresse, plus à un identifiant
La fenêtre de saisie réclamait l'Identifiant VPN à chaque ouverture sur un
poste neuf, et l'authentification échouait. Les deux symptômes n'en faisaient
qu'un : l'identifiant n'était enregistré qu'en cas d'authentification réussie,
or aucune n'aboutissait, donc il était redemandé sans fin.

Il n'est plus transmis du tout. `POST /api/session` désigne désormais le compte
par l'adresse source de la requête, comme `GET /api/session` le fait depuis la
v0.8.6. Le serveur l'a toujours su ; le poste, lui, ne faisait que répéter un
renseignement qu'il pouvait taper de travers. C'est aussi plus sûr : sur
WireGuard l'adresse source est garantie par le routage par clé, alors qu'un
identifiant en clair permettait d'ouvrir l'accès d'un compte depuis n'importe
quelle adresse. Exige la v0.3.2 du serveur, qui accepte les deux formes.

Le champ disparaît de la fenêtre de saisie. Celui du panneau Administrateur
reste, purement indicatif désormais, avec une infobulle qui le dit.

Trois décisions d'interface se fondaient sur « un identifiant est-il
configuré ? », une question que le poste ne pouvait pas trancher : proposer ou
non la saisie automatique, le libellé de la ligne Accès distant, et l'aspect du
bouton. Elles suivent maintenant la réponse du serveur, qui distingue « aucun
pair à cette adresse » (401, nouveau) de « accès fermé, code attendu » (403).
Un poste non enrôlé n'est donc plus harcelé pour un code qui n'ouvrirait rien,
et la ligne affiche « Non géré par le serveur » au lieu de le supposer.

Corrige aussi une régression de la v0.9.0 : `tunnel_gateway` retenait la route
par défaut des IPs autorisées comme réseau de tunnel, et rendait donc 0.0.0.1
comme passerelle en tunnel intégral — sondage et validation partaient vers une
adresse inexistante. Elle ne retient plus que les réseaux contenant l'adresse
du client, préfère le plus spécifique, et se replie sur /24 (ou /64) faute de
candidat. Le diagnostic « Pair joignable dans le tunnel » en bénéficie aussi.

Enfin, `authenticate` distingue le silence du réseau d'un refus du serveur,
comme `session_status` le faisait déjà.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 11:10:42 +02:00
tuxgyver b3c0515244 chore(release): v0.9.0 2026-09-08 10:02:11 +02:00
tuxgyverandClaude Opus 5 556f22e7c9 fix(auth): fiabilise la sortie de quarantaine
Audit du parcours complet — montage du tunnel, sondage de l'état, saisie
du code, ouverture de l'accès. Neuf défauts, dont plusieurs laissaient le
poste en quarantaine sans moyen visible d'en sortir : exactement ce que le
bouton « Saisir le code d'accès » existe pour éviter depuis la v0.8.2.

Adresse de l'API. api_base_url retranchait le dernier octet de l'adresse
du client, ce qui suppose un /24 et ignore les IPs autorisées. Or une
adresse client en /32 est la forme la plus courante et ne décrit aucun
réseau : un client en 10.6.1.7/32 sur un 10.6.0.0/16 était expédié vers
10.6.1.1, qui n'existe pas. La déduction passe désormais par
wireguard.tunnel_gateway, qui servait déjà au diagnostic et gère /32,
IPv6 et le repli sur les IPs autorisées. Une adresse IPv6 est encadrée de
crochets, sans quoi son « : » se confondait avec celui du port.

Révocation invisible. Le client ne sondait qu'au retour du tunnel : une
révocation avant terme — redémarrage du serveur, purge de sa table de
sessions, décision d'un administrateur — n'était annoncée par rien et
l'application affichait « Ouvert ✓ » devant un réseau muet, bouton
masqué, jusqu'à la chute du tunnel. Sans échéance annoncée, indéfiniment.
L'accès est reconfirmé chaque minute tant qu'il est cru ouvert.

Authentification perdue. « Plus tard » restait actif pendant la
vérification, Échap et la croix aussi. Fermer là rendait la main sur un
refus alors que le serveur pouvait encore accepter : sa réponse arrivait
après la sortie d'exec() et n'était lue par personne. L'accès était
ouvert côté serveur, l'application affichait la quarantaine, et le code
saisi était consommé pour rien. La fenêtre ne se ferme plus tant qu'une
requête est en vol.

Fenêtre survivante. Ni accept() ni reject() ne passent par closeEvent :
le minuteur de 500 ms continuait à battre après la fermeture, et la
fenêtre — parentée à la principale — survivait à sa disparition.
dispose() l'arrête et programme la destruction ; _access_dialog est remis
à None, son thread n'étant plus attendu à l'arrêt de l'application.

Repli 404 trompeur. La sonde d'accessibilité tenait pour preuve toute
réponse du serveur DNS du split-DNS, sans vérifier qu'il était joint par
le tunnel. Une adresse RFC1918 routée par le réseau local du poste
répondait donc hors tunnel, et le repli censé éviter le blocage le
provoquait : accès déclaré ouvert sans échéance, bouton masqué. La route
est vérifiée d'abord, sans émettre de paquet — connect() sur une socket
UDP suffit à connaître l'adresse source retenue par le noyau.

Proxy. urlopen emploie l'ouvreur par défaut, dont le ProxyHandler lit
http_proxy dans l'environnement : sur un poste d'entreprise la requête
vers l'adresse privée du tunnel partait au proxy, dont la réponse — 404
souvent — était prise pour celle du serveur, déclenchant le repli
ci-dessus. Panne intermittente parfaite : selon l'environnement de
lancement, ça passait ou non.

Silence pris pour un refus. Un ok faux couvrait « le serveur refuse » et
« le serveur n'a rien dit », traités pareil : un hoquet au montage du
tunnel faisait surgir la fenêtre et brûler un code alors que l'accès
était peut-être déjà ouvert. AuthResult porte désormais authoritative.
Un refus du serveur referme du premier coup, il en est seul juge ; un
silence déclenche un second sondage avant de déranger l'utilisateur, et
n'entame l'accès qu'après trois minutes sans réponse.

Annulation destructrice. _open_access_dialog écrivait l'état d'accès
quoi qu'il arrive. Un sondage abouti pendant que la fenêtre était ouverte
— les minuteries tournent dans la boucle imbriquée d'exec() — pouvait
établir que l'accès l'était, que « Plus tard » remettait aussitôt à
fermé. Renoncer à saisir un code ne referme rien côté serveur ; plus rien
ici non plus.

api_url. Le schéma est vérifié plutôt que repris tel quel : identifiant
et code partent dans cette URL. Reste ouvert, et hors de ce dépôt : l'API
par défaut est en clair et le serveur n'est pas authentifié.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:27:36 +02:00
tuxgyverandClaude Opus 5 205258cbf1 feat(ui): trois sous-onglets de plus dans « À propos »
Mode d'emploi, Prérequis et Sécurité rejoignent Fonctions, Garanties,
Technique, Journal et Auteur.

Le contenu vit dans app_info.py, séparé de sa présentation : le relire
ou le corriger ne demande pas de traverser le code de l'interface. Deux
aides — _about_group_label et _about_grouped — découpent en groupes
titrés les listes d'une vingtaine de cartes, sans quoi Prérequis et
Sécurité se lisaient comme un seul bloc indifférencié.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:27:09 +02:00
tuxgyverandClaude Opus 5 a989f50151 build(windows): cible windows-release, pour ne plus enchaîner à la main
La production des artefacts Windows tenait en quatre gestes manuels — make
windows, make helper, ISCC, puis le renommage — dont seul le dernier laissait
une trace visible s'il était oublié. L'ordre y était contraint sans que rien
ne le dise : wgsecure.iss source dist\wgsecure.exe et dist\wgsecure-helper.exe
sous leurs noms bruts, si bien que renommer avant de compiler l'installeur
l'aurait fait échouer, ou lui aurait fait embarquer les binaires de la version
précédente.

La cible fixe cet ordre et ajoute la vérification qui manquait : la version
vit dans ce fichier et dans wgsecure.iss, sans rien pour les synchroniser.
Bumper l'un sans l'autre produisait un installeur au mauvais numéro, que
publish ne retrouvait pas. Le contrôle est un prérequis et non une étape de
la recette : placé dans celle-ci, il n'aurait averti qu'après plusieurs
minutes de PyInstaller d'un défaut connu dès la première seconde.

La sortie d'Inno Setup part dans un journal recraché en cas d'échec
seulement : l'afficher noyait le résultat sous les avertissements wow64 de
Wine, la jeter aurait rendu toute panne muette.

Outillage uniquement : aucun changement de comportement de l'application,
donc pas d'incrément de version.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 22:47:00 +02:00
tuxgyverandClaude Opus 5 a056ad260a fix(ui): plus d'abort à l'arrêt, et temps restant de l'accès (v0.8.7)
prepare_quit() attend les threads en cours avant toute destruction, mais sa
liste en oubliait deux : le sondage d'état ajouté en 0.8.6 — ma régression —
et le thread de validation de la fenêtre de saisie, parenté à cette fenêtre
depuis la v0.8.2 et jamais attendu. Fermer la fenêtre de code pendant qu'une
requête est en vol suffisait à avorter le process, ce qui arrive dès que le
serveur tarde. Reproduit avec un serveur qui accepte sans jamais répondre,
puis vérifié corrigé sur les deux chemins.

Le sondage est ramené à 4 s : l'arrêt l'attend désormais, et dix secondes y
seraient une interface figée. Son échec ne coûte rien, contrairement à une
validation ratée qui gâche un code déjà saisi.

Ajoute le temps restant sur la ligne Accès distant, et un repli pour les
serveurs sans GET /api/session : sur un 404, le client observe si le trafic
atteint le réseau distant plutôt que de réclamer un code déjà validé. Un 403
reste une réponse qui fait autorité.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 22:33:41 +02:00
tuxgyverandClaude Opus 5 306e512aab fix(ui): ne plus réclamer un code déjà validé après une reconnexion (v0.8.6)
Le client tenait pour acquis que l'autorisation ne survivait pas à la chute
du tunnel — un commentaire l'affirmait noir sur blanc. C'est l'inverse : le
serveur autorise l'adresse de tunnel du pair jusqu'à l'échéance enregistrée,
et cette adresse ne change pas d'une reconnexion à l'autre. Se reconnecter
ne révoque rien ; seul le client oubliait, et redemandait un code que le
serveur avait déjà accepté.

Il pose maintenant la question au lieu de la supposer, via GET /api/session
(serveur >= 0.3.0), dès que le tunnel monte : démarrage sur un tunnel déjà
actif, reconnexion automatique, ou avant toute invite de saisie. Le serveur
identifie l'appelant à l'IP source, aucun identifiant ne circule.

Serveur injoignable, réponse illisible ou serveur antérieur à la 0.3.0 ne
valent pas « accès ouvert » : sans information, le client ne conclut rien et
laisse le bouton disponible — le sens sûr, et le comportement de la 0.8.5.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 18:27:32 +02:00
tuxgyverandClaude Opus 5 4cf3e26dff fix(ui): le bouton de saisie du code reparaît à l'expiration de l'accès (v0.8.5)
L'autorisation obtenue auprès du serveur a un terme, mais le client n'en
tenait aucun compte : `access_until`, pourtant renvoyé à chaque validation,
ne servait qu'à composer une ligne de journal. L'accès était marqué ouvert
jusqu'à la chute du tunnel, si bien qu'une fois le délai écoulé l'interface
affichait « Ouvert ✓ » et masquait le bouton comme l'entrée de systray —
laissant l'utilisateur devant un réseau muet, sans issue visible, dans la
situation même que ce bouton existe pour couvrir depuis la v0.8.2.

L'échéance remonte désormais jusqu'à la fenêtre principale, qui la relit
sans jamais la calculer : sa durée est un réglage serveur, que le poste n'a
pas à deviner. Un serveur qui n'en annonce pas laisse l'accès ouvert jusqu'à
la déconnexion, plutôt que de se voir imposer un terme inventé ici.

Corrige au passage l'heure de fin, annoncée en UTC mais présentée comme
locale — deux heures d'avance en été.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 17:38:03 +02:00
tuxgyverandClaude Opus 5 9083525634 fix(ui): la saisie du code ne dépend plus du champ Identifiant VPN (v0.8.4)
Le bouton et l'entrée de systray étaient conditionnés à un champ vide par
défaut depuis son introduction en v0.8.0 : un poste tenu en quarantaine par
le serveur n'affichait donc aucun moyen d'en sortir. La saisie ne dépend
plus que de l'état du tunnel ; la fenêtre demande l'identifiant quand il
manque et ne l'enregistre qu'une fois le serveur l'ayant accepté.

L'état « Non géré par le serveur » disparaît au passage : le client
l'affirmait à partir de sa seule configuration locale, sans rien vérifier.
« Non vérifié » est la vérité tant qu'aucun code n'a été validé.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 19:32:27 +02:00
tuxgyverandClaude Opus 5 1e015b5a1f fix(ui): mesurer la fenêtre après remise à plat des layouts (v0.8.3)
Le bouton « Saisir le code d'accès » était créé et placé, mais sous le bord
inférieur : rendre un widget visible ne met pas les layouts à jour dans la
foulée — Qt poste un LayoutRequest traité au tour de boucle suivant. La
fenêtre, de hauteur fixe recalculée à chaque changement de contenu, se
mesurait avant ce traitement et obtenait une hauteur en retard d'un cycle.

Le symptôme était déjà visible dans les tests de la v0.8.2 : la hauteur
montait bien de 258 à 298 px à l'apparition du bouton, mais ne redescendait
jamais à sa disparition. J'avais mis cette asymétrie sur le compte du
backend graphique de test au lieu d'y voir le retard lui-même.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 13:55:55 +02:00
tuxgyverandClaude Opus 5 996bbe240d fix(ui): rendre la saisie du code d'accès atteignable à tout moment (v0.8.2)
La fenêtre de saisie ne s'ouvrait qu'une fois, juste après une connexion
manuelle réussie. Un refus, une fenêtre fermée, une reconnexion automatique
— qui n'ouvre volontairement aucune modale — ou l'expiration de
l'autorisation au bout de 12 h laissaient un tunnel monté, un réseau muet
et aucun élément d'interface pour ressaisir un code.

Un bouton sous l'action de connexion et une entrée de systray apparaissent
désormais dès qu'une saisie a un sens : identifiant VPN configuré, tunnel
monté, accès non ouvert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 13:44:06 +02:00
tuxgyverandClaude Opus 5 cc9f521a95 feat(security): retire le MFA local, le code d'accès est celui du serveur (v0.8.1)
La v0.8.0 laissait cohabiter deux vérifications du code à 6 chiffres : la
fenêtre MFA locale, qui contrôlait un code contre un secret détenu par le
poste lui-même — et ne verrouillait donc que sa propre interface — puis la
validation par le serveur, seule à décider de l'accès réseau. L'ancienne
disparaît, avec le secret TOTP qu'elle stockait en clair dans config.json
(effacé au premier lancement) et l'onglet MFA du panneau Administrateur.

La saisie passe par une fenêtre dédiée qui relaie le refus exact du serveur
et laisse réessayer, la requête partant dans un thread pour qu'un serveur
muet ne fige pas l'application. Une ligne « Accès distant » distingue
désormais l'état de l'autorisation de celui du tunnel : monté sans code
validé, celui-ci ne mène nulle part.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 13:06:17 +02:00
tuxgyverandClaude Opus 5 dab6c1548b fix(build): ne plus vider dist/ avant de publier
`release` enchaînait `clean`, qui supprime tout dist/ — donc les artefacts
Windows compilés sous Wine juste avant, que `publish` est précisément censé
joindre à la release. Seul le binaire Linux est désormais retiré, ce qui
suffit à garantir sa fraîcheur.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:45:52 +02:00
tuxgyverandClaude Opus 5 593beaa67f chore(release): v0.8.0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:40:51 +02:00
tuxgyverandClaude Opus 5 a7ac6d1375 feat(windows): tunnel installé une fois, puis démarré — et code validé par le serveur
Deux changements liés, qui achèvent le déplacement de la sécurité vers
le serveur.

1. Le tunnel n'est plus recréé à chaque connexion. Créer ou supprimer un
   service Windows n'est jamais délégable à un utilisateur standard, le
   démarrer peut l'être : c'est cette réinstallation systématique qui
   imposait une authentification administrateur à chaque connexion ET à
   chaque déconnexion. Désormais connect() démarre le service s'il
   existe déjà, disconnect() se contente de l'arrêter, et le tunnel
   reste installé entre les deux.

   Trois voies en cascade pour ce démarrage/arrêt : sc.exe sans
   élévation, puis le service wgsecure-helper, puis l'élévation en
   dernier recours. Le helper gagne donc deux commandes
   (start/stop_tunnel_service). Cette cascade tient quelle que soit la
   configuration du poste — le test en session standard a montré que
   l'ACL posée sur le service WireGuard ne donne aucun droit sur le
   service du tunnel, qui est un objet distinct.

2. Après le montage, l'application demande le code à 6 chiffres et le
   fait valider PAR LE SERVEUR (app/core/vpn_session.py). Jusqu'ici le
   code était vérifié localement, contre un secret que l'application
   détenait : elle validait donc un code qu'elle pouvait produire, et ne
   verrouillait que sa propre interface. L'ordre est imposé par le
   réseau — l'API n'est joignable que depuis le tunnel, qui sert de
   réseau de quarantaine tant que le code n'est pas passé.

   L'URL de l'API est déduite de l'adresse du client (10.6.0.6 →
   10.6.0.1) faute de valeur explicite : c'est l'adresse du serveur
   lui-même, la seule jamais filtrée depuis la quarantaine.

Sans identifiant VPN configuré, l'étape est ignorée : le serveur peut
tourner en mode permissif, où un compte non enrôlé garde son accès, et
imposer la saisie bloquerait des installations qui fonctionnent.

Chemins Linux inchangés (branches is_windows()).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:40:34 +02:00
tuxgyverandClaude Sonnet 5 e272b9397e chore(release): v0.7.19
Sécurité : la clé privée ne subsiste plus en clair dans %APPDATA%.
Le .conf généré est écrasé puis supprimé dès que wireguard.exe l'a
consommé (il en garde sa propre copie chiffrée DPAPI), et l'import
propose d'effacer le fichier source. Windows uniquement : sous Linux
/etc/wireguard/<iface>.conf reste requis par wg-quick down.

Corrige aussi ImpersonateNamedPipeClient, qui appartient à
win32security et non à win32pipe (vérifié par introspection).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 08:36:16 +02:00
tuxgyverandClaude Sonnet 5 e96f829f8b chore(release): v0.7.18
Corrige le refus "chemin de configuration hors du dossier attendu" :
le service exigeait %ProgramData%\WireGuard\ alors que l'app écrit sa
config dans %APPDATA%\WGSecure\ (seul dossier inscriptible par un
utilisateur non-admin). Ce chemin n'étant pas recalculable depuis
LocalSystem, la validation repose désormais sur l'usurpation
d'identité du client du pipe : le fichier doit être ouvrable par
l'appelant lui-même, plus des contrôles structurels sur le nom et le
dossier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 08:17:59 +02:00
tuxgyverandClaude Sonnet 5 f73908a358 chore(release): v0.7.17
Corrige la cause racine des invites d'authentification persistantes :
le service wgsecure-helper ne démarrait jamais réellement. Lancé par
le SCM (sans commande sur la ligne d'appel), HandleCommandLine() se
contentait d'afficher son aide et de sortir, sans entrer dans le
dispatcher — SvcDoRun n'était donc jamais exécuté, aucun journal
n'était créé, le pipe n'existait jamais, et l'app retombait toujours
sur l'élévation UAC. Bascule sur PrepareToHostSingle() +
StartServiceCtrlDispatcher() quand argv est vide.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 08:03:48 +02:00
tuxgyverandClaude Sonnet 5 b4f85c5605 chore(release): v0.7.16
Durcit la boucle principale de wgsecure-helper : ne capturait que
pywintypes.error (pas Exception en général) et ne protégeait pas
CloseHandle individuellement dans le nettoyage, si bien qu'une
exception inattendue sur une requête pouvait arrêter tout le
service — expliquant un premier échange réussi (juste après
démarrage) puis un service injoignable ensuite. Gère aussi
ERROR_PIPE_CONNECTED (course normale, pas rare, entre CreateNamedPipe
et ConnectNamedPipe) au lieu de la traiter comme une erreur.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 21:50:57 +02:00
tuxgyverandClaude Sonnet 5 5133868951 chore(release): v0.7.15
Corrige helper_ipc.py : seul CreateFileW avait des argtypes/restype
ctypes explicites. Sans eux pour ReadFile/WriteFile/CloseHandle, le
HANDLE (pointeur 64 bits) était marshalé comme un entier 32 bits par
défaut et tronqué silencieusement, faisant échouer systématiquement
l'échange avec wgsecure-helper — l'app retombait donc toujours sur
son chemin élevé (2 invites UAC) malgré un service qui tournait et
répondait correctement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 21:26:32 +02:00
tuxgyverandClaude Sonnet 5 dd0df78341 chore(release): v0.7.14
Remplace l'enregistrement du service wgsecure-helper (sc.exe create/
start via [Run]) par un appel Exec()+AddQuotes en Pascal script :
l'échappement de guillemets imbriqués dans la chaîne Parameters
produisait une commande sc.exe invalide, silencieusement, alors que
la même commande fonctionnait lancée à la main. Le service n'était
donc jamais enregistré malgré une installation "réussie".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 20:52:24 +02:00
tuxgyverandClaude Sonnet 5 8ec7dc6aaf chore(release): v0.7.13
Corrige Add-DnsClientNrptRule qui échouait systématiquement : ce
cmdlet n'a pas de paramètre -Name (contrairement à Get-/Set-/
Remove-), résolu comme abréviation ambiguë entre -Namespace/
-NameServers/-NameEncoding. Identification de la règle via -Comment
à la place, comme le nettoyage générique existant. Bénéficie au
chemin élevé classique et au service wgsecure-helper.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 19:13:50 +02:00
tuxgyverandClaude Sonnet 5 7beb6deb7a chore(release): v0.7.12
Ajoute wgsecure-helper, un service Windows privilégié (LocalSystem)
qui installe/désinstalle le tunnel et pose/retire les règles NRPT
sans invite d'authentification répétée. L'ACL du service WireGuard
(v0.7.6) ne délègue que démarrer/arrêter un tunnel déjà installé,
jamais le créer/supprimer, ce que WGSecure fait pourtant à chaque
connexion/déconnexion — d'où les prompts qui persistaient malgré
v0.7.6-v0.7.11. Corrige aussi le handshake, qui échouait pour la même
raison (wg show sans élévation complète).

Purement additif : l'app retombe sur son chemin élevé existant si le
service est absent (composant optionnel dans l'installeur).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 18:15:08 +02:00
tuxgyverandClaude Sonnet 5 9dca066239 chore(release): v0.7.11
Rend le redémarrage du service WireGuard best-effort dans "Configurer
les permissions" : Restart-Service échoue si un tunnel est actif, ce
qui annulait toute l'opération alors que l'ACL/registre venaient
d'être appliqués avec succès.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 17:19:59 +02:00
tuxgyverandClaude Sonnet 5 8380172884 chore(release): v0.7.10
Détecte "déjà membre du groupe" via FullyQualifiedErrorId au lieu du
texte anglais du message d'erreur, qui est localisé (le français ne
contenait jamais "already a member", faisant échouer le bouton à
chaque nouvelle tentative une fois le groupe déjà accordé).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 17:14:26 +02:00
tuxgyverandClaude Sonnet 5 21510d13f7 chore(release): v0.7.9
Corrige la SDDL invalide (erreur 1804) produite par le fix précédent
quand le service porte une clause S: (SACL/étiquette d'intégrité), et
les accents corrompus dans les messages d'erreur Windows (mauvais
codepage/encodage lors de la capture de sortie).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 17:04:34 +02:00
tuxgyverandClaude Sonnet 5 73c102cc35 chore(release): v0.7.8
Corrige l'ACL du service WireGuard (bouton "Configurer les permissions")
qui n'était en réalité jamais posée : la SDDL était lue via
Get-WmiObject.DACL.ToString(), qui ne produit pas de SDDL valide.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 16:49:51 +02:00
tuxgyverandClaude Sonnet 5 6fb0e0dc18 chore(release): v0.7.7
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 16:23:16 +02:00
tuxgyverandClaude Sonnet 5 35e5fe1ab4 fix(windows): capture stdout/stderr élevé, tente wg show non élevé, étend la délégation
- _run_elevated_windows() capture désormais la sortie réelle d'une commande
  élevée (fichier temp écrit par le process élevé lui-même) : ShellExecuteEx
  runas ne transmet jamais les tubes hérités, donc tout message d'échec
  élevé (split-DNS NRPT, install/uninstall du tunnel) restait "erreur
  inconnue" jusqu'ici.
- run_privileged_readonly() tente réellement `wg show` sous Windows non élevé
  au lieu d'abandonner sans essayer — le handshake ne se peuplait jamais.
- Le bouton "Configurer les permissions" active aussi le mécanisme officiel
  WireGuard (LimitedOperatorUI + groupe Network Configuration Operators via
  son SID, le nom étant localisé) en plus de l'ACL du service déjà en place.
- Le diagnostic Windows précise désormais que le split-DNS (NRPT) exige
  toujours une authentification admin séparée à la connexion/déconnexion,
  aucune ACL ne pouvant le déléguer (aucun mécanisme documenté trouvé).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 16:21:35 +02:00
tuxgyverandClaude Sonnet 5 7a8a5de1a4 fix: passe --version explicitement dans make publish
gitea_release.sh ne sait détecter la version que depuis VERSION,
*/version.py, package.json, pyproject.toml ou Cargo.toml — pas
app/__init__.py utilisé par ce dépôt. Le Makefile connaît déjà
VERSION, autant le lui passer directement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 15:30:27 +02:00
tuxgyverandClaude Sonnet 5 4d8abc07d3 build: ajoute la cible make publish
Enchaîne release (build Linux versionné) puis gitea_release.sh
(BorisSkill) pour taguer et publier sur Gitea, en joignant aussi les
artefacts Windows déjà présents dans dist/ pour la version courante
s'ils existent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 15:29:11 +02:00
tuxgyverandClaude Sonnet 5 6a3e8700c3 chore(release): v0.7.6
Ajoute un fichier log texte rotatif (wgsecure.log) dans le dossier
applicatif, en complément du journal JSON interne, pour diagnostiquer
un échec (ex. split-DNS) sans dépendre de l'UI. Corrige le panneau
Journal qui coupait les messages longs sans moyen de les lire en
entier (défilement horizontal réactivé + infobulle).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 15:25:01 +02:00
tuxgyverandClaude Sonnet 5 66faf5e661 chore(release): v0.7.5
Corrige l'échec silencieux « Accès refusé » à la connexion Windows :
wireguard.exe /install|uninstalltunnelservice était lancé sans élévation
en misant sur son manifeste UAC natif, qui ne s'auto-déclenche jamais via
un appel process-à-process. L'appel tente maintenant sans élévation
(compatible avec les ACLs accordées via le bouton dédié), puis retente
avec une vraie élévation UAC si le premier essai échoue. Le diagnostic
d'élévation est mis à jour pour refléter les deux cas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 14:24:56 +02:00
tuxgyver 3b869aeae8 docs: mise à jour de la page À propos pour v0.7.4
La version s'affiche déjà dynamiquement depuis app.__version__ ; seul le
contenu curaté (FEATURES) devait suivre les apports réels de la 0.7.4 :
diagnostic du split-DNS et verrouillage des champs après import .conf.
2026-09-04 09:18:25 +02:00
tuxgyver 92d3d7f96e docs: mise à jour README pour v0.7.4
Badges de version et règles sudoers manuelles resynchronisées avec les
deux nouvelles commandes resolvectl (dns/domain) du split-DNS par lien.
2026-09-04 09:16:57 +02:00
tuxgyver 835d8e2bfb chore: synchronise la version de l'installeur Windows (0.7.4)
wgsecure.iss dupliquait encore 0.7.3, manqué lors du bump de version —
l'installeur généré aurait porté le mauvais numéro.
2026-09-04 09:06:15 +02:00
tuxgyver 5c53cf2500 chore(release): v0.7.4
Split-DNS repose désormais sur resolvectl par interface (Linux) et une
règle NRPT complète (Windows), avec diagnostic de résolution distante,
verrouillage des champs après import d'un .conf, et consolidation des
couleurs de l'UI dans theme.py.
2026-09-04 08:57:52 +02:00
tuxgyver 902c177330 Add Remote DNS 2026-09-04 08:55:35 +02:00
tuxgyver 910c04f845 docs: mise à jour README, CHANGELOG et page About pour v0.7.3
- Mise à jour du README avec la version 0.7.3 et le badge
- Mise à jour du CHANGELOG avec les notes de version 0.7.3
- Ajout du Split-DNS dans les fonctionnalités de la page About

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-09-03 07:21:26 +02:00
tuxgyver 38b48fc428 feat: support split-DNS, infobulles UI et corrections Windows
- Ajout du support complet du split-DNS pour Linux (systemd-resolved) et Windows (NRPT)
- Bouton de configuration des permissions ACL pour WireGuard sous Windows
- Suppression de tous les appels à sc.exe (remplacés par PowerShell + wireguard.exe)
- UAC gérée par wireguard.exe lui-même (pas par WGSecure)
- Infobulles ajoutées sur tous les champs de configuration principaux
- Correction du blocage du handshake lié au redémarrage de systemd-resolved
- Incrémentation de la version à 0.7.3

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-09-03 07:20:13 +02:00