From e0c48f2921557b5e95ae006ad186d6c7c3330078 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Fri, 4 Sep 2026 04:42:01 -0400 Subject: [PATCH] =?UTF-8?q?[UPD]=20changelog=20:=20six=20entr=C3=A9es=20po?= =?UTF-8?q?ur=20les=20outils=20git=20et=20le=20menu=20TODO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cinq correctifs et une icône de menu passaient d'une branche à la production sans qu'un lecteur qui décide de mettre à jour puisse le savoir : les cinq commits QEMU du lot étaient décrits, ceux des outils git et du menu non. Le non-publié les porte désormais, avec le mode de défaillance retiré et le chiffre qui le borne — une page HTML de 34 ko avalée par « sh », un lot de plus de cent dépôts abandonné, 49 tests et une erreur d'import devenus 84. Les six entrées suivent le même ordre dans les deux langues, la source étant CHANGELOG.base.md, et make doc_markdown a régénéré la paire. --- EN --- Five fixes and one menu icon went from a branch to production with no way for a reader deciding whether to upgrade to know it: the batch's five QEMU commits were described, those of the git tools and of the menu were not. The unreleased section now carries them, with the failure mode removed and the figure that bounds it — a 34 kB HTML page swallowed by `sh`, a batch of more than a hundred repositories abandoned, 49 tests and an import error turned into 84. The six entries follow the same order in both languages, the source being CHANGELOG.base.md, and make doc_markdown regenerated the pair. Assisted-by: Claude Opus 5 --- CHANGELOG.base.md | 12 ++++++++++++ CHANGELOG.fr.md | 6 ++++++ CHANGELOG.md | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/CHANGELOG.base.md b/CHANGELOG.base.md index 922c0a3..bb5529a 100644 --- a/CHANGELOG.base.md +++ b/CHANGELOG.base.md @@ -251,6 +251,7 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi ## Modifié +- The RTK menu shows an icon before each of its six entries, the last menu whose lines were told apart by their number alone; the glyphs are taken from those the file already uses rather than picked for themselves, a recent character rendering as tofu on a terminal whose font does not know it - The name of a VM built from a rolling release drops the version segment: `latest` distinguishes no VM from another. A named version that coexists with others in the catalogue stays - Every menu entry carries an icon, ten menus having stayed bare, and the spacing follows the RENDERED width read from Unicode rather than guessed — two spaces after a one-column emoji, one after a wide one. The QEMU Manage section, grown too long to scan, is split into Manage, VM access and Troubleshoot - Staging names the files, never `git add -A`. The sweep stages everything untracked, and this repository keeps two directories untracked ON PURPOSE: `private/`, the only place allowed to hold customer data, and `tasks/`, where the convention sends the investigation precisely because it is not versioned. It also swallows whatever else is in flight in the checkout, under a subject that does not cover it; `git add -p` stages the hunks when one file carries two subjects @@ -261,6 +262,7 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi +- Le menu RTK affiche une icône devant chacune de ses six entrées, dernier menu dont les lignes ne se distinguaient que par leur numéro ; les glyphes sont pris parmi ceux que le fichier emploie déjà plutôt que choisis pour eux-mêmes, un caractère récent s'affichant en tofu sur un terminal dont la police l'ignore - Le nom d'une VM bâtie sur une publication continue perd le segment de version : « latest » ne distingue aucune VM d'une autre. Une version nommée qui coexiste avec d'autres au catalogue y reste - Chaque entrée de menu porte une icône, dix menus étant restés nus, et l'espacement suit la largeur RENDUE lue dans Unicode plutôt que devinée — deux espaces derrière un emoji d'une colonne, une derrière un large. La section Gérer de QEMU, devenue trop longue à parcourir, est scindée en Gérer, Accès à la VM et Dépannage - L'indexation nomme les fichiers, jamais `git add -A`. Le ratissage indexe tout ce qui n'est pas suivi, et le dépôt garde deux répertoires non suivis EXPRÈS : `private/`, seul endroit autorisé à porter une donnée de client, et `tasks/`, où la convention envoie l'enquête précisément parce qu'il n'est pas versionné. Il emporte aussi ce qui est en cours ailleurs dans le checkout, sous un sujet qui ne le couvre pas ; `git add -p` indexe les hunks quand un fichier porte deux sujets @@ -279,6 +281,11 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi - A libvirt network no longer counts as its own collision. A started network carries and routes its /24 on its bridge, and that bridge was read as « the host already occupies this », so the verdict was « collision » on every machine where the network ran, whatever subnet it served. `--setup-host` — and every deployment, which calls the same check — then tore the network down and moved it onto a free /24 where nothing conflicted, leaving the VMs attached to it without a gateway and with a detached tap. The bridge of the network being examined is now excluded from what the host occupies, addresses being read one interface per line so each address can be tied to its own; an unreadable bridge name excludes nothing, silence weighing on the cautious side. The XML handed to `net-define` goes through a real temporary file, unpredictable and removed even when virsh fails, where a name composed of the network's own was a guessable path in a directory everyone writes to - `--setup-host` no longer leaves a machine that loses its network at the next boot. libvirt's `default` network serves 192.168.122.0/24, and every VM this repository deploys LIVES in that network: its bridge would take the .1 address, which is that machine's own gateway. virsh refuses such a start — but only while the route is there, and at boot libvirtd raises its networks BEFORE the host's DHCP lease arrives: nothing signals the collision any more, virbr0 takes the gateway address, and the host has no network left. Autostart was armed even when the start had just failed, and the message then advised a reboot. The network is now MOVED onto a free /24 by redefinition, which needs neither bridge nor kernel module and therefore works where the start does not, keeping its UUID, bridge name and MAC so the domains naming it still find it; autostart is armed only where no collision remains, and REMOVED where one does. A network found active on a route of the host is torn down first — that is the already-broken machine, and it is what gives the host its network back, before anything else needs downloading. One reboot still suffices where the only obstacle is a kernel replaced since boot - The state of a libvirt network is read in English. virsh TRANSLATES its labels: under a French locale `net-info` answers « Actif : non », where a pattern on `Active: yes` never matches — so every network read as off, `--setup-host` declared the host not ready whatever its state, and its advice was to reboot for nothing. Every parsed virsh output now goes through one call that forces `LC_ALL=C`, the same gesture, for the same reason, as the QEMU management screen +- The starship installer no longer feeds a 404 page to a shell. The upstream URL pointed at a path that does not exist; the server answers it with a 34 kB HTML page, which `curl -sS` writes to its output anyway while exiting 0, so `sh` stopped on `Syntax error` at its second line — a diagnosis naming neither the wrong URL nor the fact that nothing was installed. The URL is corrected, and `-f` makes curl write nothing and return 22 on any error response, as the file's four other upstream installers already did +- A question asked by a menu command is seen before it is answered. Output was read line by line, so a prompt carrying no newline stayed held back until the following line and reached the screen AFTER the answer: every `[y/N]` was answered blind. Reading now happens in chunks, the trailing fragment is shown at once, and an incremental decoder protects accented characters split across two reads +- One faulty repository no longer takes a whole remote-rewriting batch down with it. A directory emptied by hand passes the is-a-directory test then makes opening the repository raise — an interrupted clone, a deleted `.git`, both common on a development checkout — which abandoned every repository left in a batch of more than a hundred. Such a repository is skipped, the deviations are listed at the END rather than printed as they happen where the trace drowns them, and the exit code stays zero +- The commit-msg hook finds its own library without hiding GitPython. Putting `script/` at the head of `sys.path` made `import git` resolve to the repository's own `script/git/` package rather than to GitPython, for every module imported AFTERWARDS in the same process; the repository root did the same to the standard `test` package. Both go to the tail instead, so installed packages keep their precedence — `unittest discover -p 'test_git*.py'` goes from 49 tests and an import error to 84 green +- A checkout without `.repo` keeps its root repository. Two early returns jumped over the handling of the root, so a missing manifest returned an empty list although the root had been asked for: the script that rewrites the remotes found nothing to do and reported it as a success. Adding the root and sorting go through a single exit point, and a missing origin falls back to the default URL rather than raising - The KeePassXC vault opens on a machine without tkinter, which is every server. Both imports shared a single `try`, so a missing tkinter set PyKeePass to None as well: the vault stayed unopenable even with path and password configured, while the log said `pykeepass is not installed` and pykeepass 4.2 was there. tkinter serves only the file picker, when no path is configured. The prompt also names the vault before asking for its password, rather than after - The QEMU menu goes through the `libvirt` group rather than sudo, which added no right and asked for a password at every entry; membership is settled by TRYING, never by reading /etc/group. The libvirt URI is named explicitly: without `--connect`, a non-root virsh targets `qemu:///session`, a SEPARATE hypervisor where no system VM exists, and `list --all` returns an empty list with no error — root's default URI had masked the omission - System tools launched from the menu no longer inherit the venv at the head of their PATH. A Python tool bootstrapped by `env python3` started in an interpreter without the distribution's modules and died on `No module named 'gi'` @@ -309,6 +316,11 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi - Un réseau libvirt ne compte plus comme sa propre collision. Un réseau démarré porte et route son /24 sur son pont, et ce pont était lu comme « l'hôte occupe déjà ceci » : le verdict était donc « collision » sur toute machine où le réseau tournait, quel que soit le sous-réseau qu'il servait. `--setup-host` — et tout déploiement, qui passe par la même vérification — abattait alors le réseau et le déplaçait sur un /24 libre là où rien n'entrait en conflit, laissant les VM qui y étaient attachées sans passerelle et le tap détaché. Le pont du réseau examiné est désormais écarté de ce que l'hôte occupe, les adresses étant lues une interface par ligne pour que chacune se rattache à la sienne ; un nom de pont illisible n'écarte rien, le silence pesant du côté prudent. Le XML remis à « net-define » passe par un vrai fichier temporaire, imprévisible et retiré même quand virsh échoue, là où un nom composé de celui du réseau était un chemin devinable dans un répertoire où tout le monde écrit - `--setup-host` ne laisse plus derrière lui une machine qui perd son réseau au démarrage suivant. Le réseau « default » de libvirt sert 192.168.122.0/24, et toute VM déployée par ce dépôt VIT dans ce réseau : son pont y prendrait l'adresse .1, celle de sa propre passerelle. virsh refuse ce démarrage — mais seulement tant que la route est là, et au démarrage libvirtd monte ses réseaux AVANT que le bail DHCP de l'hôte n'arrive : plus rien ne signale la collision, virbr0 prend l'adresse de la passerelle, et l'hôte n'a plus de réseau. L'autostart était armé même quand le démarrage venait d'échouer, et le message invitait alors à redémarrer. Le réseau est désormais DÉPLACÉ sur un /24 libre par redéfinition — laquelle ne demande ni pont ni module du noyau, donc elle passe là où le démarrage ne passe pas — en gardant son UUID, le nom de son pont et son adresse MAC, si bien que les domaines qui le nomment le retrouvent ; l'autostart ne s'arme que là où aucune collision ne reste, et se RETIRE là où il en reste une. Un réseau trouvé actif sur une route de l'hôte est abattu d'abord : c'est la machine déjà cassée, et c'est ce qui lui rend son accès au réseau, avant même qu'il y ait de quoi télécharger un correctif. Un seul redémarrage suffit toujours quand le seul obstacle est un noyau remplacé depuis le démarrage - L'état d'un réseau libvirt est lu en anglais. virsh TRADUIT ses étiquettes : sous une locale française, « net-info » répond « Actif : non », où un motif sur « Active: yes » ne trouve jamais rien — tout réseau se lisait donc éteint, « --setup-host » déclarait l'hôte pas prêt quel que soit son état, et son conseil était de redémarrer pour rien. Toute sortie de virsh que l'on analyse passe maintenant par un appel unique qui force « LC_ALL=C », le même geste, pour la même raison, que l'écran de gestion QEMU +- L'installateur de starship ne fait plus avaler une page 404 à un shell. L'URL amont pointait sur un chemin qui n'existe pas ; le serveur y répond par une page HTML de 34 ko, que `curl -sS` écrit tout de même sur sa sortie en rendant 0, si bien que `sh` s'arrêtait sur `Syntax error` à sa deuxième ligne — un diagnostic qui ne dit ni l'URL fautive ni que rien n'a été posé. L'URL est corrigée, et `-f` fait taire curl et rendre 22 sur toute réponse d'erreur, comme le faisaient déjà les quatre autres installateurs amont du fichier +- Une question posée par une commande du menu se voit avant qu'on y réponde. La sortie était lue ligne par ligne, donc une invite sans saut de ligne restait retenue jusqu'à la ligne suivante et arrivait à l'écran APRÈS la réponse : tout « [o/N] » se répondait à l'aveugle. La lecture se fait désormais par blocs, le reliquat est montré aussitôt, et un décodeur incrémental protège les accents coupés entre deux lectures +- Un dépôt fautif n'emporte plus tout un lot de réécriture de remotes. Un répertoire vidé à la main passe le test du répertoire puis fait lever l'ouverture du dépôt — clone interrompu, `.git` effacé, deux états courants sur un checkout de développement — ce qui abandonnait tous les dépôts restants d'un lot de plus de cent. Un tel dépôt est ignoré, les écarts sont listés à la FIN plutôt qu'affichés au fil de l'eau où la trace les noie, et la sortie reste nulle +- Le hook commit-msg trouve sa bibliothèque sans masquer GitPython. Mettre `script/` en tête de `sys.path` faisait résoudre `import git` vers le paquet `script/git/` du dépôt plutôt que vers GitPython, pour tout module importé ENSUITE dans le même processus ; la racine du dépôt faisait de même au paquet standard `test`. Les deux passent en queue, si bien que les paquets installés gardent la priorité — `unittest discover -p 'test_git*.py'` passe de 49 tests et une erreur d'import à 84 verts +- Un checkout sans `.repo` garde son dépôt racine. Deux retours anticipés sautaient par-dessus le traitement de la racine, si bien qu'un manifeste absent rendait une liste vide alors que la racine était demandée : le script qui réécrit les remotes ne trouvait rien à faire et l'annonçait comme un succès. L'ajout de la racine et le tri passent par un point de sortie unique, et une origine absente vaut l'URL par défaut plutôt qu'une exception - Le coffre KeePassXC s'ouvre sur une machine sans tkinter, c'est-à-dire sur tout serveur. Les deux imports partageaient un seul `try`, si bien que l'absence de tkinter mettait aussi PyKeePass à None : le coffre restait inouvrable même avec chemin et mot de passe configurés, alors que le journal annonçait « pykeepass is not installed » et que pykeepass 4.2 était là. tkinter ne sert qu'au sélecteur de fichier, quand aucun chemin n'est configuré. L'invite nomme aussi le coffre avant d'en demander le mot de passe, et non après - Le menu QEMU passe par le groupe « libvirt » plutôt que par sudo, qui n'ajoutait aucun droit et réclamait un mot de passe à chaque entrée ; l'appartenance se tranche en ESSAYANT, jamais en lisant /etc/group. L'URI libvirt est nommée explicitement : sans « --connect », un virsh non root vise « qemu:///session », un hyperviseur SÉPARÉ où aucune VM du système n'existe, et « list --all » y rend une liste vide sans erreur — l'URI par défaut de root masquait l'omission - Les outils système lancés depuis le menu n'héritent plus du venv en tête de leur PATH. Un outil écrit en Python et amorcé par « env python3 » démarrait dans un interpréteur privé des modules de la distribution et sortait sur « No module named 'gi' » diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md index c426612..3582f38 100644 --- a/CHANGELOG.fr.md +++ b/CHANGELOG.fr.md @@ -119,6 +119,7 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi ## Modifié +- Le menu RTK affiche une icône devant chacune de ses six entrées, dernier menu dont les lignes ne se distinguaient que par leur numéro ; les glyphes sont pris parmi ceux que le fichier emploie déjà plutôt que choisis pour eux-mêmes, un caractère récent s'affichant en tofu sur un terminal dont la police l'ignore - Le nom d'une VM bâtie sur une publication continue perd le segment de version : « latest » ne distingue aucune VM d'une autre. Une version nommée qui coexiste avec d'autres au catalogue y reste - Chaque entrée de menu porte une icône, dix menus étant restés nus, et l'espacement suit la largeur RENDUE lue dans Unicode plutôt que devinée — deux espaces derrière un emoji d'une colonne, une derrière un large. La section Gérer de QEMU, devenue trop longue à parcourir, est scindée en Gérer, Accès à la VM et Dépannage - L'indexation nomme les fichiers, jamais `git add -A`. Le ratissage indexe tout ce qui n'est pas suivi, et le dépôt garde deux répertoires non suivis EXPRÈS : `private/`, seul endroit autorisé à porter une donnée de client, et `tasks/`, où la convention envoie l'enquête précisément parce qu'il n'est pas versionné. Il emporte aussi ce qui est en cours ailleurs dans le checkout, sous un sujet qui ne le couvre pas ; `git add -p` indexe les hunks quand un fichier porte deux sujets @@ -133,6 +134,11 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi - Un réseau libvirt ne compte plus comme sa propre collision. Un réseau démarré porte et route son /24 sur son pont, et ce pont était lu comme « l'hôte occupe déjà ceci » : le verdict était donc « collision » sur toute machine où le réseau tournait, quel que soit le sous-réseau qu'il servait. `--setup-host` — et tout déploiement, qui passe par la même vérification — abattait alors le réseau et le déplaçait sur un /24 libre là où rien n'entrait en conflit, laissant les VM qui y étaient attachées sans passerelle et le tap détaché. Le pont du réseau examiné est désormais écarté de ce que l'hôte occupe, les adresses étant lues une interface par ligne pour que chacune se rattache à la sienne ; un nom de pont illisible n'écarte rien, le silence pesant du côté prudent. Le XML remis à « net-define » passe par un vrai fichier temporaire, imprévisible et retiré même quand virsh échoue, là où un nom composé de celui du réseau était un chemin devinable dans un répertoire où tout le monde écrit - `--setup-host` ne laisse plus derrière lui une machine qui perd son réseau au démarrage suivant. Le réseau « default » de libvirt sert 192.168.122.0/24, et toute VM déployée par ce dépôt VIT dans ce réseau : son pont y prendrait l'adresse .1, celle de sa propre passerelle. virsh refuse ce démarrage — mais seulement tant que la route est là, et au démarrage libvirtd monte ses réseaux AVANT que le bail DHCP de l'hôte n'arrive : plus rien ne signale la collision, virbr0 prend l'adresse de la passerelle, et l'hôte n'a plus de réseau. L'autostart était armé même quand le démarrage venait d'échouer, et le message invitait alors à redémarrer. Le réseau est désormais DÉPLACÉ sur un /24 libre par redéfinition — laquelle ne demande ni pont ni module du noyau, donc elle passe là où le démarrage ne passe pas — en gardant son UUID, le nom de son pont et son adresse MAC, si bien que les domaines qui le nomment le retrouvent ; l'autostart ne s'arme que là où aucune collision ne reste, et se RETIRE là où il en reste une. Un réseau trouvé actif sur une route de l'hôte est abattu d'abord : c'est la machine déjà cassée, et c'est ce qui lui rend son accès au réseau, avant même qu'il y ait de quoi télécharger un correctif. Un seul redémarrage suffit toujours quand le seul obstacle est un noyau remplacé depuis le démarrage - L'état d'un réseau libvirt est lu en anglais. virsh TRADUIT ses étiquettes : sous une locale française, « net-info » répond « Actif : non », où un motif sur « Active: yes » ne trouve jamais rien — tout réseau se lisait donc éteint, « --setup-host » déclarait l'hôte pas prêt quel que soit son état, et son conseil était de redémarrer pour rien. Toute sortie de virsh que l'on analyse passe maintenant par un appel unique qui force « LC_ALL=C », le même geste, pour la même raison, que l'écran de gestion QEMU +- L'installateur de starship ne fait plus avaler une page 404 à un shell. L'URL amont pointait sur un chemin qui n'existe pas ; le serveur y répond par une page HTML de 34 ko, que `curl -sS` écrit tout de même sur sa sortie en rendant 0, si bien que `sh` s'arrêtait sur `Syntax error` à sa deuxième ligne — un diagnostic qui ne dit ni l'URL fautive ni que rien n'a été posé. L'URL est corrigée, et `-f` fait taire curl et rendre 22 sur toute réponse d'erreur, comme le faisaient déjà les quatre autres installateurs amont du fichier +- Une question posée par une commande du menu se voit avant qu'on y réponde. La sortie était lue ligne par ligne, donc une invite sans saut de ligne restait retenue jusqu'à la ligne suivante et arrivait à l'écran APRÈS la réponse : tout « [o/N] » se répondait à l'aveugle. La lecture se fait désormais par blocs, le reliquat est montré aussitôt, et un décodeur incrémental protège les accents coupés entre deux lectures +- Un dépôt fautif n'emporte plus tout un lot de réécriture de remotes. Un répertoire vidé à la main passe le test du répertoire puis fait lever l'ouverture du dépôt — clone interrompu, `.git` effacé, deux états courants sur un checkout de développement — ce qui abandonnait tous les dépôts restants d'un lot de plus de cent. Un tel dépôt est ignoré, les écarts sont listés à la FIN plutôt qu'affichés au fil de l'eau où la trace les noie, et la sortie reste nulle +- Le hook commit-msg trouve sa bibliothèque sans masquer GitPython. Mettre `script/` en tête de `sys.path` faisait résoudre `import git` vers le paquet `script/git/` du dépôt plutôt que vers GitPython, pour tout module importé ENSUITE dans le même processus ; la racine du dépôt faisait de même au paquet standard `test`. Les deux passent en queue, si bien que les paquets installés gardent la priorité — `unittest discover -p 'test_git*.py'` passe de 49 tests et une erreur d'import à 84 verts +- Un checkout sans `.repo` garde son dépôt racine. Deux retours anticipés sautaient par-dessus le traitement de la racine, si bien qu'un manifeste absent rendait une liste vide alors que la racine était demandée : le script qui réécrit les remotes ne trouvait rien à faire et l'annonçait comme un succès. L'ajout de la racine et le tri passent par un point de sortie unique, et une origine absente vaut l'URL par défaut plutôt qu'une exception - Le coffre KeePassXC s'ouvre sur une machine sans tkinter, c'est-à-dire sur tout serveur. Les deux imports partageaient un seul `try`, si bien que l'absence de tkinter mettait aussi PyKeePass à None : le coffre restait inouvrable même avec chemin et mot de passe configurés, alors que le journal annonçait « pykeepass is not installed » et que pykeepass 4.2 était là. tkinter ne sert qu'au sélecteur de fichier, quand aucun chemin n'est configuré. L'invite nomme aussi le coffre avant d'en demander le mot de passe, et non après - Le menu QEMU passe par le groupe « libvirt » plutôt que par sudo, qui n'ajoutait aucun droit et réclamait un mot de passe à chaque entrée ; l'appartenance se tranche en ESSAYANT, jamais en lisant /etc/group. L'URI libvirt est nommée explicitement : sans « --connect », un virsh non root vise « qemu:///session », un hyperviseur SÉPARÉ où aucune VM du système n'existe, et « list --all » y rend une liste vide sans erreur — l'URI par défaut de root masquait l'omission - Les outils système lancés depuis le menu n'héritent plus du venv en tête de leur PATH. Un outil écrit en Python et amorcé par « env python3 » démarrait dans un interpréteur privé des modules de la distribution et sortait sur « No module named 'gi' » diff --git a/CHANGELOG.md b/CHANGELOG.md index dcd5ee3..b8c491c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,6 +117,7 @@ Recreating the virtual environment, use installation guide from tool `make`. ## Changed +- The RTK menu shows an icon before each of its six entries, the last menu whose lines were told apart by their number alone; the glyphs are taken from those the file already uses rather than picked for themselves, a recent character rendering as tofu on a terminal whose font does not know it - The name of a VM built from a rolling release drops the version segment: `latest` distinguishes no VM from another. A named version that coexists with others in the catalogue stays - Every menu entry carries an icon, ten menus having stayed bare, and the spacing follows the RENDERED width read from Unicode rather than guessed — two spaces after a one-column emoji, one after a wide one. The QEMU Manage section, grown too long to scan, is split into Manage, VM access and Troubleshoot - Staging names the files, never `git add -A`. The sweep stages everything untracked, and this repository keeps two directories untracked ON PURPOSE: `private/`, the only place allowed to hold customer data, and `tasks/`, where the convention sends the investigation precisely because it is not versioned. It also swallows whatever else is in flight in the checkout, under a subject that does not cover it; `git add -p` stages the hunks when one file carries two subjects @@ -131,6 +132,11 @@ Recreating the virtual environment, use installation guide from tool `make`. - A libvirt network no longer counts as its own collision. A started network carries and routes its /24 on its bridge, and that bridge was read as « the host already occupies this », so the verdict was « collision » on every machine where the network ran, whatever subnet it served. `--setup-host` — and every deployment, which calls the same check — then tore the network down and moved it onto a free /24 where nothing conflicted, leaving the VMs attached to it without a gateway and with a detached tap. The bridge of the network being examined is now excluded from what the host occupies, addresses being read one interface per line so each address can be tied to its own; an unreadable bridge name excludes nothing, silence weighing on the cautious side. The XML handed to `net-define` goes through a real temporary file, unpredictable and removed even when virsh fails, where a name composed of the network's own was a guessable path in a directory everyone writes to - `--setup-host` no longer leaves a machine that loses its network at the next boot. libvirt's `default` network serves 192.168.122.0/24, and every VM this repository deploys LIVES in that network: its bridge would take the .1 address, which is that machine's own gateway. virsh refuses such a start — but only while the route is there, and at boot libvirtd raises its networks BEFORE the host's DHCP lease arrives: nothing signals the collision any more, virbr0 takes the gateway address, and the host has no network left. Autostart was armed even when the start had just failed, and the message then advised a reboot. The network is now MOVED onto a free /24 by redefinition, which needs neither bridge nor kernel module and therefore works where the start does not, keeping its UUID, bridge name and MAC so the domains naming it still find it; autostart is armed only where no collision remains, and REMOVED where one does. A network found active on a route of the host is torn down first — that is the already-broken machine, and it is what gives the host its network back, before anything else needs downloading. One reboot still suffices where the only obstacle is a kernel replaced since boot - The state of a libvirt network is read in English. virsh TRANSLATES its labels: under a French locale `net-info` answers « Actif : non », where a pattern on `Active: yes` never matches — so every network read as off, `--setup-host` declared the host not ready whatever its state, and its advice was to reboot for nothing. Every parsed virsh output now goes through one call that forces `LC_ALL=C`, the same gesture, for the same reason, as the QEMU management screen +- The starship installer no longer feeds a 404 page to a shell. The upstream URL pointed at a path that does not exist; the server answers it with a 34 kB HTML page, which `curl -sS` writes to its output anyway while exiting 0, so `sh` stopped on `Syntax error` at its second line — a diagnosis naming neither the wrong URL nor the fact that nothing was installed. The URL is corrected, and `-f` makes curl write nothing and return 22 on any error response, as the file's four other upstream installers already did +- A question asked by a menu command is seen before it is answered. Output was read line by line, so a prompt carrying no newline stayed held back until the following line and reached the screen AFTER the answer: every `[y/N]` was answered blind. Reading now happens in chunks, the trailing fragment is shown at once, and an incremental decoder protects accented characters split across two reads +- One faulty repository no longer takes a whole remote-rewriting batch down with it. A directory emptied by hand passes the is-a-directory test then makes opening the repository raise — an interrupted clone, a deleted `.git`, both common on a development checkout — which abandoned every repository left in a batch of more than a hundred. Such a repository is skipped, the deviations are listed at the END rather than printed as they happen where the trace drowns them, and the exit code stays zero +- The commit-msg hook finds its own library without hiding GitPython. Putting `script/` at the head of `sys.path` made `import git` resolve to the repository's own `script/git/` package rather than to GitPython, for every module imported AFTERWARDS in the same process; the repository root did the same to the standard `test` package. Both go to the tail instead, so installed packages keep their precedence — `unittest discover -p 'test_git*.py'` goes from 49 tests and an import error to 84 green +- A checkout without `.repo` keeps its root repository. Two early returns jumped over the handling of the root, so a missing manifest returned an empty list although the root had been asked for: the script that rewrites the remotes found nothing to do and reported it as a success. Adding the root and sorting go through a single exit point, and a missing origin falls back to the default URL rather than raising - The KeePassXC vault opens on a machine without tkinter, which is every server. Both imports shared a single `try`, so a missing tkinter set PyKeePass to None as well: the vault stayed unopenable even with path and password configured, while the log said `pykeepass is not installed` and pykeepass 4.2 was there. tkinter serves only the file picker, when no path is configured. The prompt also names the vault before asking for its password, rather than after - The QEMU menu goes through the `libvirt` group rather than sudo, which added no right and asked for a password at every entry; membership is settled by TRYING, never by reading /etc/group. The libvirt URI is named explicitly: without `--connect`, a non-root virsh targets `qemu:///session`, a SEPARATE hypervisor where no system VM exists, and `list --all` returns an empty list with no error — root's default URI had masked the omission - System tools launched from the menu no longer inherit the venv at the head of their PATH. A Python tool bootstrapped by `env python3` started in an interpreter without the distribution's modules and died on `No module named 'gi'`