From 7ce14e2929279c0eb368511e95f0cb4f5684cd88 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Thu, 3 Sep 2026 04:37:33 -0400 Subject: [PATCH] [UPD] changelog : la branche qemu 3D, diagnostic et outils --- CHANGELOG.base.md | 26 ++++++++++++++++++++++++++ CHANGELOG.fr.md | 13 +++++++++++++ CHANGELOG.md | 13 +++++++++++++ 3 files changed, 52 insertions(+) diff --git a/CHANGELOG.base.md b/CHANGELOG.base.md index 6a8f298..9b46999 100644 --- a/CHANGELOG.base.md +++ b/CHANGELOG.base.md @@ -41,6 +41,12 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi ## Ajouté +- 3D acceleration for QEMU VMs, ticked at creation and settable afterwards, even on a VM with NO virtual screen — `auto` never grants one there, abstaining rather than adding a video device nobody asked for, while an off-screen render or an emulator inside the VM wants exactly that. A render node can exist while EGL refuses to start on it: QEMU then rejects the domain and the VM stays unusable until someone undoes the setting, so creation falls back to software rendering and an existing VM is offered the removal. Inside the guest the render node is `root:render` at 0660 and the account was not in it, so every GL application fell back to software rendering although VIRGL negotiation had succeeded, with nothing to say so; `render` and `video` are now declared BEFORE use, an unknown group name making cloud-init create no account at all — no password, no SSH key, a VM that boots unreachable +- A QEMU diagnostic report, written to one file to hand to someone who has no access to the machine: twenty-one read-only probes — host, hypervisor, GPU, tools present, storage — each time-bounded, since a command that hangs must not hold the report, and each section isolated, the file being written in one block at the end. It states the 3D condition of every VM from its PERSISTENT definition, where three values answer together and none alone: video type, `accel3d`, and the device libvirt pinned. That last one, an attribute added in libvirt 12.5.0 to keep the guest ABI stable across restarts, OUTRANKS `accel3d`: a VM first started without 3D keeps the non-GL device, and ticking the box afterwards writes an intent nothing applies. The report also offers the tools missing from it, showing the full command before asking, and the device list QEMU may open — libvirt adds the render node when the domain declares it, never a proprietary card's own nodes, which that stack opens too. It names the host, its paths and its addresses, and says so before it is shared +- Recovering files from the disk of a VM that no longer boots, libguestfs mounting its qcow2 without it. Every command carries `--ro`, and that is what changes the manoeuvre: opening the disk of a running machine for writing corrupts its filesystem. Partitions are listed, then the directories to copy out, the `copy-out` commands being shown rather than guessed at +- Development assistants installed INSIDE a VM at deployment: rtk, starship with its shell hook, and one agent — Claude Code or opencode. The box reveals the choice and the git identity, prefilled from the host, since that is what the VM already receives and an empty field would suggest none; what is typed wins, field by field. Every install is denied stdin and time-bounded: `|| true` covers failure, not WAITING, and an upstream installer asking a question would hang on a terminal-less SSH — hence `-y` for starship +- A Git and Shell menu that installs what a checkout needs rather than printing a command to copy: the repository's git hooks, `merge.conflictStyle=zdiff3`, Starship, Claude Code, opencode, and the Claude Code plugins with an ERPLibre list. Three assistant commands are deployed from it — `/git_prepare_merge`, `/todo_plan_max`, `/todo_generate_code`. The missing tools of a safe shrink are installed across the four package families, where three separate pieces of code each knew a different subset. A binary posed in a HOME directory the shell's PATH does not always carry is found anyway, and the export line is written once +- A deployment blocked by an orphan disk — the qcow2 an interrupted creation leaves behind, which `deploy_qemu` then refuses to overwrite — is offered its deletion, size and path shown, before the creation fails after having made you wait - A `pre-commit` hook lists the comments worth re-reading in the files being staged, and never blocks: over the repository's own sources, 373 files yield 463 signals, and a blocking check at that scale gets uninstalled the following week. The tool behind it, `script/analyse/check_comment_hygiene.py`, reports two families of unequal certainty — identifying data, an address, an e-mail or an account path, which is a finding; and narrative, a witness marker, an absolute date or the first person, which is a signal to RE-READ, since it cannot know whether the sentence states a durable fact. It reads comments and docstrings, `#` lines and shell trailing comments alike, skips vendored code, and falls back on a line scan when a source will not parse, an empty report otherwise declaring clean a file it never read. Exit codes follow the repository convention: 0 nothing to report, 1 findings, 2 the tool failed - The TODO menu shows the context an assistant is given, scattered as it is over six sources: instructions, rules, skills, deployed commands, git hooks and memory. Each deployed command is compared against its repository template, ignoring the git identity lines the deployment substitutes, so a stale copy shows up where a strict equality would declare them all stale. Nothing from `private/` is reported, not even a count: naming a file whose purpose is to hold what must not go out amounts to pointing at it - Duplicating a database and neutralising it for good: duplication goes through Odoo's `exp_duplicate_database` rather than `CREATE DATABASE … TEMPLATE`, which copies the tables and nothing else. Only Odoo drops the source's open connections — one Odoo shell left open is enough for PostgreSQL to refuse — regenerates `database.uuid`, copies the filestore, and runs the `neutralize.sql` files of the installed modules. The repository's in-house modules obtained none of the four, and one of them opened a door: deleting every `ir.mail_server` makes Odoo fall back on the configuration file's `smtp_server`, where Odoo's own placeholder server exists precisely to plug that hole. Odoo's neutralisation begins at 16; from 12 to 15 the copy falls back on the repository's long-standing `update_prod_to_dev.sh`, which sets no `is_neutralized`, disables no cron and leaves the payment keys, but does remove the mail servers and lay down a development account. The route taken is ANNOUNCED at run time, a copy whose route is unknown being unjudgeable, and a missing script fails the copy rather than letting it come out raw while announcing itself neutralised @@ -133,6 +139,12 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi +- L'accélération 3D des VM QEMU, cochée à la création et réglable ensuite, même sur une VM SANS écran virtuel — « auto » ne l'accorde jamais là, s'abstenant plutôt que de poser un périphérique vidéo que personne n'a demandé, alors qu'un rendu hors écran ou un émulateur tournant dedans veut exactement cela. Un nœud de rendu peut exister sans qu'EGL y démarre : QEMU refuse alors le domaine et la VM reste inutilisable jusqu'à ce que quelqu'un défasse le réglage, d'où un repli sur le rendu logiciel à la création et le retrait proposé sur une VM existante. Dans l'invité, le nœud de rendu appartient à « root:render » en 0660 et le compte n'y était pas : toute application GL retombait sur le rendu logiciel alors que la négociation VIRGL avait réussi, sans que rien ne le signale ; « render » et « video » sont désormais déclarés AVANT usage, un nom de groupe inconnu faisant que cloud-init ne crée aucun compte — ni mot de passe, ni clé SSH, une VM qui démarre injoignable +- Un diagnostic QEMU, écrit dans un fichier unique à transmettre à quelqu'un qui n'a pas accès à la machine : vingt et une sondes en lecture — hôte, hyperviseur, GPU, outils présents, stockage — chacune bornée dans le temps, une commande qui pend ne devant pas retenir le rapport, et chaque section isolée, le fichier s'écrivant d'un bloc à la fin. Il dit l'état 3D de chaque VM d'après sa définition PERSISTANTE, où trois valeurs répondent ensemble et aucune seule : le type de vidéo, « accel3d », et le device figé par libvirt. Ce dernier, un attribut arrivé avec libvirt 12.5.0 pour tenir l'ABI de l'invité stable d'un démarrage à l'autre, L'EMPORTE sur « accel3d » : une VM démarrée une première fois sans 3D garde le device sans GL, et cocher la case ensuite écrit une intention que rien n'applique. Le rapport propose aussi les outils qui lui manquent, la commande complète affichée avant la question, et la liste des périphériques que QEMU peut ouvrir — libvirt y met le nœud de rendu quand le domaine le déclare, jamais les nœuds propres d'une carte propriétaire, que sa pile ouvre pourtant. Il porte le nom de l'hôte, ses chemins et ses adresses, et le dit avant qu'on l'envoie +- La récupération de fichiers dans le disque d'une VM qui ne démarre plus, libguestfs montant son qcow2 sans elle. Toute commande porte « --ro », et c'est ce qui change la manœuvre : ouvrir en écriture le disque d'une machine allumée corrompt son système de fichiers. Les partitions sont listées, puis les répertoires à extraire, les commandes « copy-out » étant montrées plutôt que devinées +- Les assistants de développement installés DANS une VM au déploiement : rtk, starship avec son accroche au shell, et un agent — Claude Code ou opencode. La case découvre le choix et l'identité git, pré-remplie avec celle de l'hôte, puisque c'est ce que la VM reçoit déjà et qu'un champ vide la ferait croire absente ; ce qui est saisi prime, champ par champ. Chaque pose est privée d'entrée standard et bornée dans le temps : « || true » couvre l'échec, pas l'ATTENTE, et un installateur amont qui pose une question resterait pendu sur un SSH sans terminal — d'où « -y » pour starship +- Un menu Git et Shell qui installe ce dont un clone a besoin au lieu d'afficher une commande à recopier : les hooks git du dépôt, « merge.conflictStyle=zdiff3 », Starship, Claude Code, opencode, et les plugins Claude Code avec une liste ERPLibre. Trois commandes d'assistant s'y déploient — « /git_prepare_merge », « /todo_plan_max », « /todo_generate_code ». Les outils manquants d'une réduction sûre s'installent sur les quatre familles de paquets, là où trois écritures séparées en connaissaient chacune un sous-ensemble différent. Un binaire posé dans un répertoire du HOME que le PATH du shell ne porte pas toujours est trouvé quand même, et la ligne d'export est écrite une seule fois +- Un déploiement bloqué par un disque orphelin — le qcow2 qu'une création interrompue laisse et que « deploy_qemu » refuse ensuite d'écraser — se voit proposer son effacement, taille et chemin affichés, avant que la création n'échoue après avoir fait attendre - Un hook `pre-commit` liste les commentaires à relire dans les fichiers qu'on indexe, et ne bloque jamais : sur les sources du dépôt, 373 fichiers rendent 463 signaux, et un contrôle bloquant à cette échelle se fait désinstaller la semaine suivante. L'outil qui le sert, `script/analyse/check_comment_hygiene.py`, rapporte deux familles de sûreté inégale — la donnée identifiante, adresse, courriel ou chemin de compte, qui est une trouvaille ; et le récit, marqueur de témoignage, date absolue ou première personne, qui est un signal à RELIRE, l'outil ne pouvant savoir si la phrase énonce un fait durable. Il lit les commentaires et les docstrings, les lignes `#` comme les commentaires shell de fin de ligne, écarte le code tiers, et se replie sur un balayage ligne à ligne quand un source ne se parse pas, un rapport vide déclarant sinon propre un fichier qu'il n'a jamais lu. Les codes de sortie suivent la convention du dépôt : 0 rien à signaler, 1 des trouvailles, 2 l'outil a échoué - Le menu TODO montre le contexte fourni à un assistant, éparpillé sur six sources : instructions, règles, skills, commandes déployées, hooks git et mémoire. Chaque commande déployée est comparée à son gabarit du dépôt en ignorant les lignes d'identité git que le déploiement substitue, si bien qu'une copie périmée se voit là où une égalité stricte les déclarerait toutes périmées. Rien de `private/` n'est relevé, pas même un compte : nommer un fichier dont l'objet est de retenir ce qui ne doit pas sortir revient à le désigner - Dupliquer une base et la neutraliser pour de bon : la duplication passe par `exp_duplicate_database` d'Odoo plutôt que par `CREATE DATABASE … TEMPLATE`, qui copie les tables et rien d'autre. Odoo seul coupe les connexions ouvertes sur la source — un shell Odoo laissé ouvert suffit à faire refuser PostgreSQL —, régénère `database.uuid`, copie le filestore et exécute les fichiers `neutralize.sql` des modules installés. Les modules maison du dépôt n'obtenaient aucun des quatre, et l'un d'eux ouvrait une porte : supprimer tous les `ir.mail_server` fait retomber Odoo sur le `smtp_server` du fichier de configuration, là où le serveur bouchon d'Odoo existe précisément pour boucher ce trou. La neutralisation d'Odoo commence à la 16 ; de 12 à 15 la copie retombe sur la technique de longue date du dépôt, `update_prod_to_dev.sh`, qui ne pose pas `is_neutralized`, ne désactive aucun cron et laisse les clés de paiement, mais supprime les serveurs de courriel et pose un compte de développement. Le chemin suivi est ANNONCÉ à l'exécution, une copie dont on ignore par quel chemin elle est passée ne se jugeant pas, et un script introuvable fait échouer la copie plutôt que de la laisser sortir brute en s'annonçant neutralisée @@ -231,6 +243,8 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi ## Modifié +- 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 - A countdown prompt gives 15 seconds to decide, where five were not enough to READ the question: the countdown exists so a run can be left unattended, not to go fast, and too short it does the opposite — the answer comes by reflex, or a default no one read is taken. A restored database is named after the backup file, which already carries a telling name, rather than « test », under which successive migrations all landed on one name. The name is sanitised, since it ends up in a createdb, and capped at 41 characters: the driver appends « _neutralize_upgrade_18 » and PostgreSQL truncates at 63, which would put two tiers on the same name. A remote download keeps the name the server gave - todo.py split into nine files, one per subject, with a shared base per form. It carried 9 500 lines more than a file should and every subject went through it; the deployment forms repeated the same field-and-validation machinery, so a fix in one never reached the others. No behaviour changes @@ -239,6 +253,8 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi +- 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 - Une invite à compte à rebours laisse 15 secondes pour décider, là où cinq ne suffisaient pas à LIRE la question : le compte à rebours n'existe pas pour aller vite mais pour qu'une exécution puisse être laissée sans surveillance, et trop court il fait l'inverse — la réponse vient par réflexe, ou un défaut que personne n'a lu s'applique. Une base restaurée porte le nom du fichier de sauvegarde, qui en porte déjà un parlant, plutôt que « test », sous lequel des migrations successives finissaient toutes sur un même nom. Le nom est assaini, puisqu'il finit dans un createdb, et borné à 41 caractères : le pilote ajoute « _neutralize_upgrade_18 » et PostgreSQL tronque à 63, ce qui ferait finir deux paliers sur le même nom. Un téléchargement distant garde celui que le serveur a donné - todo.py éclaté en neuf fichiers, un par sujet, avec un socle commun par formulaire. Il portait 9 500 lignes de plus qu'un fichier ne devrait et tous les sujets y passaient ; les formulaires de déploiement répétaient la même mécanique de champs et de validation, si bien qu'une correction dans l'un ne gagnait jamais les autres. Aucun changement de comportement @@ -251,6 +267,11 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi ## Corrigé +- 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'` +- Accepting to install the QEMU packages no longer reboots the host without asking: `--assume-yes` covered the package manager, and the command silently added `--reboot-if-needed`. One constant served both the disposable guest and the workstation +- Three screens that fell over: the statistics screen, where `datetime.datetime` does not exist since the CLASS is imported and the error took all of TODO with it; a failed VM whose output showed four lines of epilogue instead of the tool's own message; and a duplicate i18n key silently overwriting a main-menu label, the last definition winning in a Python dict literal with neither error nor warning +- A shrink measures the free space BEFORE offering the backup, which doubles the space used and defaulted to YES: on an almost full disk, an empty answer started a copy that stopped halfway and left a truncated `.bak` - `odoo_bin.sh db --drop` failed with AccessDenied at every migration tier, and the clone then hit « database already exists »: db_restore.py reads the repository's config.conf, sees admin_passwd = admin and therefore sends no master password, while odoo_bin.sh passed no « -c », so Odoo read ~/.odoorc and its hashed one. ODOO_RC closes that seam in one place instead of twenty call sites, versions 12 to 18 reading it after « -c » and before ~/.odoorc, so an explicit choice still wins - The account.root SQL view Odoo 17 creates is dropped before the load into 18, where the model carries _auto = False and _table_query = '0', so its name enters no query and the missing-table check skips it. The view is not merely dead but WRONG, built on the code column the 18 ORM no longer writes, and it is the sole pin holding the two legacy columns database_cleanup fails on — no DROP COLUMN, OpenUpgrade still reading them afterwards - The migration repair created pricelists that should not exist, in two ways. It asked `env.user.has_group()`, which says yes as soon as the caller belongs to the group — and the migration adds it along the way — where the settings checkbox reads something else entirely: what `base.group_user` IMPLIES. Deciding on the caller built a pricelist in a database whose feature is off, and Odoo then warned on every opening of the settings that it would archive it. And a pricelist shared across companies, with an empty `company_id`, did not count as belonging to the company: the repair saw a company without a list and made an empty duplicate beside the existing one, on a database that had lost nothing. Both now read the group implication and the tool's own `pricelist_missing` detector, which already tells the two cases apart; the migration-residue check asked the same wrong question and was corrected with them @@ -271,6 +292,11 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi - The NAT bridge was written before knowing whether NAT exists. Six lines of iptables and "return code 1" came after the stanza had already gone into /etc/network/interfaces, and nothing in that noise said a reboot was needed: the host was running Debian's cloud kernel, stripped of netfilter. Our own install_proxmox.sh produces that state, so a freshly installed nested Proxmox is ALWAYS in it — the guard now sits where the consequence is, not at host confirmation +- 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' » +- Accepter d'installer les paquets QEMU ne redémarre plus l'hôte sans demander : « --assume-yes » couvrait le gestionnaire de paquets, et la commande y ajoutait « --reboot-if-needed » en silence. Une seule constante servait l'invité jetable et le poste de travail +- Trois écrans qui tombaient : les statistiques, où « datetime.datetime » n'existe pas puisque c'est la CLASSE qui est importée et où l'erreur emportait TODO entier ; une VM échouée dont la sortie montrait quatre lignes d'épilogue au lieu du message de l'outil ; et une clé i18n dupliquée qui écrasait un libellé du menu principal, la dernière définition gagnant dans un littéral de dict Python sans erreur ni avertissement +- Une réduction mesure la place libre AVANT de proposer la sauvegarde, qui double la place occupée et avait OUI pour défaut : sur un disque presque plein, une réponse vide lançait une copie qui s'arrête à mi-course et laisse un « .bak » tronqué - « odoo_bin.sh db --drop » échouait par AccessDenied à chaque palier de migration, et le clone butait ensuite sur « database already exists » : db_restore.py lit le config.conf du dépôt, y voit admin_passwd = admin et n'envoie donc aucun mot de passe maître, quand odoo_bin.sh ne passait pas de « -c », si bien qu'Odoo lisait ~/.odoorc et son mot de passe haché. ODOO_RC ferme cette couture en un point plutôt qu'à vingt sites d'appel, les versions 12 à 18 le lisant après « -c » et avant ~/.odoorc, donc un choix explicite l'emporte toujours - La vue SQL account.root que crée Odoo 17 est retirée avant le chargement en 18, où le modèle porte _auto = False et _table_query = '0', si bien que son nom n'entre plus dans aucune requête et que le contrôle des tables manquantes l'ignore. La vue n'est pas seulement morte mais FAUSSE, bâtie sur la colonne code que l'ORM 18 n'écrit plus, et elle est l'unique épingle des deux colonnes héritées où database_cleanup échoue — aucun DROP COLUMN, OpenUpgrade les lisant encore ensuite - La réparation de migration créait des listes de prix qui n'avaient pas lieu d'être, de deux façons. Elle interrogeait `env.user.has_group()`, qui répond oui dès que l'exécutant est membre du groupe — et la migration l'y ajoute en cours de route —, là où la case des réglages lit tout autre chose : ce que `base.group_user` IMPLIQUE. Décider sur l'exécutant créait une liste de prix dans une base dont la fonctionnalité est éteinte, et Odoo prévenait alors à chaque ouverture des réglages qu'il allait l'archiver. Et une liste de prix partagée entre sociétés, au `company_id` vide, ne comptait pas comme appartenant à la société : la réparation voyait une société sans liste et fabriquait un doublon vide à côté de l'existante, sur une base qui n'avait pourtant rien perdu. Les deux lisent désormais l'implication du groupe et le détecteur `pricelist_missing` du même outil, qui distingue déjà les deux cas ; le contrôle « restant de migration » posait la même mauvaise question et a été corrigé avec elles diff --git a/CHANGELOG.fr.md b/CHANGELOG.fr.md index 015acdf..8b8aca6 100644 --- a/CHANGELOG.fr.md +++ b/CHANGELOG.fr.md @@ -15,6 +15,12 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi ## Ajouté +- L'accélération 3D des VM QEMU, cochée à la création et réglable ensuite, même sur une VM SANS écran virtuel — « auto » ne l'accorde jamais là, s'abstenant plutôt que de poser un périphérique vidéo que personne n'a demandé, alors qu'un rendu hors écran ou un émulateur tournant dedans veut exactement cela. Un nœud de rendu peut exister sans qu'EGL y démarre : QEMU refuse alors le domaine et la VM reste inutilisable jusqu'à ce que quelqu'un défasse le réglage, d'où un repli sur le rendu logiciel à la création et le retrait proposé sur une VM existante. Dans l'invité, le nœud de rendu appartient à « root:render » en 0660 et le compte n'y était pas : toute application GL retombait sur le rendu logiciel alors que la négociation VIRGL avait réussi, sans que rien ne le signale ; « render » et « video » sont désormais déclarés AVANT usage, un nom de groupe inconnu faisant que cloud-init ne crée aucun compte — ni mot de passe, ni clé SSH, une VM qui démarre injoignable +- Un diagnostic QEMU, écrit dans un fichier unique à transmettre à quelqu'un qui n'a pas accès à la machine : vingt et une sondes en lecture — hôte, hyperviseur, GPU, outils présents, stockage — chacune bornée dans le temps, une commande qui pend ne devant pas retenir le rapport, et chaque section isolée, le fichier s'écrivant d'un bloc à la fin. Il dit l'état 3D de chaque VM d'après sa définition PERSISTANTE, où trois valeurs répondent ensemble et aucune seule : le type de vidéo, « accel3d », et le device figé par libvirt. Ce dernier, un attribut arrivé avec libvirt 12.5.0 pour tenir l'ABI de l'invité stable d'un démarrage à l'autre, L'EMPORTE sur « accel3d » : une VM démarrée une première fois sans 3D garde le device sans GL, et cocher la case ensuite écrit une intention que rien n'applique. Le rapport propose aussi les outils qui lui manquent, la commande complète affichée avant la question, et la liste des périphériques que QEMU peut ouvrir — libvirt y met le nœud de rendu quand le domaine le déclare, jamais les nœuds propres d'une carte propriétaire, que sa pile ouvre pourtant. Il porte le nom de l'hôte, ses chemins et ses adresses, et le dit avant qu'on l'envoie +- La récupération de fichiers dans le disque d'une VM qui ne démarre plus, libguestfs montant son qcow2 sans elle. Toute commande porte « --ro », et c'est ce qui change la manœuvre : ouvrir en écriture le disque d'une machine allumée corrompt son système de fichiers. Les partitions sont listées, puis les répertoires à extraire, les commandes « copy-out » étant montrées plutôt que devinées +- Les assistants de développement installés DANS une VM au déploiement : rtk, starship avec son accroche au shell, et un agent — Claude Code ou opencode. La case découvre le choix et l'identité git, pré-remplie avec celle de l'hôte, puisque c'est ce que la VM reçoit déjà et qu'un champ vide la ferait croire absente ; ce qui est saisi prime, champ par champ. Chaque pose est privée d'entrée standard et bornée dans le temps : « || true » couvre l'échec, pas l'ATTENTE, et un installateur amont qui pose une question resterait pendu sur un SSH sans terminal — d'où « -y » pour starship +- Un menu Git et Shell qui installe ce dont un clone a besoin au lieu d'afficher une commande à recopier : les hooks git du dépôt, « merge.conflictStyle=zdiff3 », Starship, Claude Code, opencode, et les plugins Claude Code avec une liste ERPLibre. Trois commandes d'assistant s'y déploient — « /git_prepare_merge », « /todo_plan_max », « /todo_generate_code ». Les outils manquants d'une réduction sûre s'installent sur les quatre familles de paquets, là où trois écritures séparées en connaissaient chacune un sous-ensemble différent. Un binaire posé dans un répertoire du HOME que le PATH du shell ne porte pas toujours est trouvé quand même, et la ligne d'export est écrite une seule fois +- Un déploiement bloqué par un disque orphelin — le qcow2 qu'une création interrompue laisse et que « deploy_qemu » refuse ensuite d'écraser — se voit proposer son effacement, taille et chemin affichés, avant que la création n'échoue après avoir fait attendre - Un hook `pre-commit` liste les commentaires à relire dans les fichiers qu'on indexe, et ne bloque jamais : sur les sources du dépôt, 373 fichiers rendent 463 signaux, et un contrôle bloquant à cette échelle se fait désinstaller la semaine suivante. L'outil qui le sert, `script/analyse/check_comment_hygiene.py`, rapporte deux familles de sûreté inégale — la donnée identifiante, adresse, courriel ou chemin de compte, qui est une trouvaille ; et le récit, marqueur de témoignage, date absolue ou première personne, qui est un signal à RELIRE, l'outil ne pouvant savoir si la phrase énonce un fait durable. Il lit les commentaires et les docstrings, les lignes `#` comme les commentaires shell de fin de ligne, écarte le code tiers, et se replie sur un balayage ligne à ligne quand un source ne se parse pas, un rapport vide déclarant sinon propre un fichier qu'il n'a jamais lu. Les codes de sortie suivent la convention du dépôt : 0 rien à signaler, 1 des trouvailles, 2 l'outil a échoué - Le menu TODO montre le contexte fourni à un assistant, éparpillé sur six sources : instructions, règles, skills, commandes déployées, hooks git et mémoire. Chaque commande déployée est comparée à son gabarit du dépôt en ignorant les lignes d'identité git que le déploiement substitue, si bien qu'une copie périmée se voit là où une égalité stricte les déclarerait toutes périmées. Rien de `private/` n'est relevé, pas même un compte : nommer un fichier dont l'objet est de retenir ce qui ne doit pas sortir revient à le désigner - Dupliquer une base et la neutraliser pour de bon : la duplication passe par `exp_duplicate_database` d'Odoo plutôt que par `CREATE DATABASE … TEMPLATE`, qui copie les tables et rien d'autre. Odoo seul coupe les connexions ouvertes sur la source — un shell Odoo laissé ouvert suffit à faire refuser PostgreSQL —, régénère `database.uuid`, copie le filestore et exécute les fichiers `neutralize.sql` des modules installés. Les modules maison du dépôt n'obtenaient aucun des quatre, et l'un d'eux ouvrait une porte : supprimer tous les `ir.mail_server` fait retomber Odoo sur le `smtp_server` du fichier de configuration, là où le serveur bouchon d'Odoo existe précisément pour boucher ce trou. La neutralisation d'Odoo commence à la 16 ; de 12 à 15 la copie retombe sur la technique de longue date du dépôt, `update_prod_to_dev.sh`, qui ne pose pas `is_neutralized`, ne désactive aucun cron et laisse les clés de paiement, mais supprime les serveurs de courriel et pose un compte de développement. Le chemin suivi est ANNONCÉ à l'exécution, une copie dont on ignore par quel chemin elle est passée ne se jugeant pas, et un script introuvable fait échouer la copie plutôt que de la laisser sortir brute en s'annonçant neutralisée @@ -109,6 +115,8 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi ## Modifié +- 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 - Une invite à compte à rebours laisse 15 secondes pour décider, là où cinq ne suffisaient pas à LIRE la question : le compte à rebours n'existe pas pour aller vite mais pour qu'une exécution puisse être laissée sans surveillance, et trop court il fait l'inverse — la réponse vient par réflexe, ou un défaut que personne n'a lu s'applique. Une base restaurée porte le nom du fichier de sauvegarde, qui en porte déjà un parlant, plutôt que « test », sous lequel des migrations successives finissaient toutes sur un même nom. Le nom est assaini, puisqu'il finit dans un createdb, et borné à 41 caractères : le pilote ajoute « _neutralize_upgrade_18 » et PostgreSQL tronque à 63, ce qui ferait finir deux paliers sur le même nom. Un téléchargement distant garde celui que le serveur a donné - todo.py éclaté en neuf fichiers, un par sujet, avec un socle commun par formulaire. Il portait 9 500 lignes de plus qu'un fichier ne devrait et tous les sujets y passaient ; les formulaires de déploiement répétaient la même mécanique de champs et de validation, si bien qu'une correction dans l'un ne gagnait jamais les autres. Aucun changement de comportement @@ -117,6 +125,11 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi ## Corrigé +- 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' » +- Accepter d'installer les paquets QEMU ne redémarre plus l'hôte sans demander : « --assume-yes » couvrait le gestionnaire de paquets, et la commande y ajoutait « --reboot-if-needed » en silence. Une seule constante servait l'invité jetable et le poste de travail +- Trois écrans qui tombaient : les statistiques, où « datetime.datetime » n'existe pas puisque c'est la CLASSE qui est importée et où l'erreur emportait TODO entier ; une VM échouée dont la sortie montrait quatre lignes d'épilogue au lieu du message de l'outil ; et une clé i18n dupliquée qui écrasait un libellé du menu principal, la dernière définition gagnant dans un littéral de dict Python sans erreur ni avertissement +- Une réduction mesure la place libre AVANT de proposer la sauvegarde, qui double la place occupée et avait OUI pour défaut : sur un disque presque plein, une réponse vide lançait une copie qui s'arrête à mi-course et laisse un « .bak » tronqué - « odoo_bin.sh db --drop » échouait par AccessDenied à chaque palier de migration, et le clone butait ensuite sur « database already exists » : db_restore.py lit le config.conf du dépôt, y voit admin_passwd = admin et n'envoie donc aucun mot de passe maître, quand odoo_bin.sh ne passait pas de « -c », si bien qu'Odoo lisait ~/.odoorc et son mot de passe haché. ODOO_RC ferme cette couture en un point plutôt qu'à vingt sites d'appel, les versions 12 à 18 le lisant après « -c » et avant ~/.odoorc, donc un choix explicite l'emporte toujours - La vue SQL account.root que crée Odoo 17 est retirée avant le chargement en 18, où le modèle porte _auto = False et _table_query = '0', si bien que son nom n'entre plus dans aucune requête et que le contrôle des tables manquantes l'ignore. La vue n'est pas seulement morte mais FAUSSE, bâtie sur la colonne code que l'ORM 18 n'écrit plus, et elle est l'unique épingle des deux colonnes héritées où database_cleanup échoue — aucun DROP COLUMN, OpenUpgrade les lisant encore ensuite - La réparation de migration créait des listes de prix qui n'avaient pas lieu d'être, de deux façons. Elle interrogeait `env.user.has_group()`, qui répond oui dès que l'exécutant est membre du groupe — et la migration l'y ajoute en cours de route —, là où la case des réglages lit tout autre chose : ce que `base.group_user` IMPLIQUE. Décider sur l'exécutant créait une liste de prix dans une base dont la fonctionnalité est éteinte, et Odoo prévenait alors à chaque ouverture des réglages qu'il allait l'archiver. Et une liste de prix partagée entre sociétés, au `company_id` vide, ne comptait pas comme appartenant à la société : la réparation voyait une société sans liste et fabriquait un doublon vide à côté de l'existante, sur une base qui n'avait pourtant rien perdu. Les deux lisent désormais l'implication du groupe et le détecteur `pricelist_missing` du même outil, qui distingue déjà les deux cas ; le contrôle « restant de migration » posait la même mauvaise question et a été corrigé avec elles diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f174d..e79ca7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,12 @@ Recreating the virtual environment, use installation guide from tool `make`. ## Added +- 3D acceleration for QEMU VMs, ticked at creation and settable afterwards, even on a VM with NO virtual screen — `auto` never grants one there, abstaining rather than adding a video device nobody asked for, while an off-screen render or an emulator inside the VM wants exactly that. A render node can exist while EGL refuses to start on it: QEMU then rejects the domain and the VM stays unusable until someone undoes the setting, so creation falls back to software rendering and an existing VM is offered the removal. Inside the guest the render node is `root:render` at 0660 and the account was not in it, so every GL application fell back to software rendering although VIRGL negotiation had succeeded, with nothing to say so; `render` and `video` are now declared BEFORE use, an unknown group name making cloud-init create no account at all — no password, no SSH key, a VM that boots unreachable +- A QEMU diagnostic report, written to one file to hand to someone who has no access to the machine: twenty-one read-only probes — host, hypervisor, GPU, tools present, storage — each time-bounded, since a command that hangs must not hold the report, and each section isolated, the file being written in one block at the end. It states the 3D condition of every VM from its PERSISTENT definition, where three values answer together and none alone: video type, `accel3d`, and the device libvirt pinned. That last one, an attribute added in libvirt 12.5.0 to keep the guest ABI stable across restarts, OUTRANKS `accel3d`: a VM first started without 3D keeps the non-GL device, and ticking the box afterwards writes an intent nothing applies. The report also offers the tools missing from it, showing the full command before asking, and the device list QEMU may open — libvirt adds the render node when the domain declares it, never a proprietary card's own nodes, which that stack opens too. It names the host, its paths and its addresses, and says so before it is shared +- Recovering files from the disk of a VM that no longer boots, libguestfs mounting its qcow2 without it. Every command carries `--ro`, and that is what changes the manoeuvre: opening the disk of a running machine for writing corrupts its filesystem. Partitions are listed, then the directories to copy out, the `copy-out` commands being shown rather than guessed at +- Development assistants installed INSIDE a VM at deployment: rtk, starship with its shell hook, and one agent — Claude Code or opencode. The box reveals the choice and the git identity, prefilled from the host, since that is what the VM already receives and an empty field would suggest none; what is typed wins, field by field. Every install is denied stdin and time-bounded: `|| true` covers failure, not WAITING, and an upstream installer asking a question would hang on a terminal-less SSH — hence `-y` for starship +- A Git and Shell menu that installs what a checkout needs rather than printing a command to copy: the repository's git hooks, `merge.conflictStyle=zdiff3`, Starship, Claude Code, opencode, and the Claude Code plugins with an ERPLibre list. Three assistant commands are deployed from it — `/git_prepare_merge`, `/todo_plan_max`, `/todo_generate_code`. The missing tools of a safe shrink are installed across the four package families, where three separate pieces of code each knew a different subset. A binary posed in a HOME directory the shell's PATH does not always carry is found anyway, and the export line is written once +- A deployment blocked by an orphan disk — the qcow2 an interrupted creation leaves behind, which `deploy_qemu` then refuses to overwrite — is offered its deletion, size and path shown, before the creation fails after having made you wait - A `pre-commit` hook lists the comments worth re-reading in the files being staged, and never blocks: over the repository's own sources, 373 files yield 463 signals, and a blocking check at that scale gets uninstalled the following week. The tool behind it, `script/analyse/check_comment_hygiene.py`, reports two families of unequal certainty — identifying data, an address, an e-mail or an account path, which is a finding; and narrative, a witness marker, an absolute date or the first person, which is a signal to RE-READ, since it cannot know whether the sentence states a durable fact. It reads comments and docstrings, `#` lines and shell trailing comments alike, skips vendored code, and falls back on a line scan when a source will not parse, an empty report otherwise declaring clean a file it never read. Exit codes follow the repository convention: 0 nothing to report, 1 findings, 2 the tool failed - The TODO menu shows the context an assistant is given, scattered as it is over six sources: instructions, rules, skills, deployed commands, git hooks and memory. Each deployed command is compared against its repository template, ignoring the git identity lines the deployment substitutes, so a stale copy shows up where a strict equality would declare them all stale. Nothing from `private/` is reported, not even a count: naming a file whose purpose is to hold what must not go out amounts to pointing at it - Duplicating a database and neutralising it for good: duplication goes through Odoo's `exp_duplicate_database` rather than `CREATE DATABASE … TEMPLATE`, which copies the tables and nothing else. Only Odoo drops the source's open connections — one Odoo shell left open is enough for PostgreSQL to refuse — regenerates `database.uuid`, copies the filestore, and runs the `neutralize.sql` files of the installed modules. The repository's in-house modules obtained none of the four, and one of them opened a door: deleting every `ir.mail_server` makes Odoo fall back on the configuration file's `smtp_server`, where Odoo's own placeholder server exists precisely to plug that hole. Odoo's neutralisation begins at 16; from 12 to 15 the copy falls back on the repository's long-standing `update_prod_to_dev.sh`, which sets no `is_neutralized`, disables no cron and leaves the payment keys, but does remove the mail servers and lay down a development account. The route taken is ANNOUNCED at run time, a copy whose route is unknown being unjudgeable, and a missing script fails the copy rather than letting it come out raw while announcing itself neutralised @@ -107,6 +113,8 @@ Recreating the virtual environment, use installation guide from tool `make`. ## Changed +- 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 - A countdown prompt gives 15 seconds to decide, where five were not enough to READ the question: the countdown exists so a run can be left unattended, not to go fast, and too short it does the opposite — the answer comes by reflex, or a default no one read is taken. A restored database is named after the backup file, which already carries a telling name, rather than « test », under which successive migrations all landed on one name. The name is sanitised, since it ends up in a createdb, and capped at 41 characters: the driver appends « _neutralize_upgrade_18 » and PostgreSQL truncates at 63, which would put two tiers on the same name. A remote download keeps the name the server gave - todo.py split into nine files, one per subject, with a shared base per form. It carried 9 500 lines more than a file should and every subject went through it; the deployment forms repeated the same field-and-validation machinery, so a fix in one never reached the others. No behaviour changes @@ -115,6 +123,11 @@ Recreating the virtual environment, use installation guide from tool `make`. ## Fixed +- 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'` +- Accepting to install the QEMU packages no longer reboots the host without asking: `--assume-yes` covered the package manager, and the command silently added `--reboot-if-needed`. One constant served both the disposable guest and the workstation +- Three screens that fell over: the statistics screen, where `datetime.datetime` does not exist since the CLASS is imported and the error took all of TODO with it; a failed VM whose output showed four lines of epilogue instead of the tool's own message; and a duplicate i18n key silently overwriting a main-menu label, the last definition winning in a Python dict literal with neither error nor warning +- A shrink measures the free space BEFORE offering the backup, which doubles the space used and defaulted to YES: on an almost full disk, an empty answer started a copy that stopped halfway and left a truncated `.bak` - `odoo_bin.sh db --drop` failed with AccessDenied at every migration tier, and the clone then hit « database already exists »: db_restore.py reads the repository's config.conf, sees admin_passwd = admin and therefore sends no master password, while odoo_bin.sh passed no « -c », so Odoo read ~/.odoorc and its hashed one. ODOO_RC closes that seam in one place instead of twenty call sites, versions 12 to 18 reading it after « -c » and before ~/.odoorc, so an explicit choice still wins - The account.root SQL view Odoo 17 creates is dropped before the load into 18, where the model carries _auto = False and _table_query = '0', so its name enters no query and the missing-table check skips it. The view is not merely dead but WRONG, built on the code column the 18 ORM no longer writes, and it is the sole pin holding the two legacy columns database_cleanup fails on — no DROP COLUMN, OpenUpgrade still reading them afterwards - The migration repair created pricelists that should not exist, in two ways. It asked `env.user.has_group()`, which says yes as soon as the caller belongs to the group — and the migration adds it along the way — where the settings checkbox reads something else entirely: what `base.group_user` IMPLIES. Deciding on the caller built a pricelist in a database whose feature is off, and Odoo then warned on every opening of the settings that it would archive it. And a pricelist shared across companies, with an empty `company_id`, did not count as belonging to the company: the repair saw a company without a list and made an empty duplicate beside the existing one, on a database that had lost nothing. Both now read the group implication and the tool's own `pricelist_missing` detector, which already tells the two cases apart; the migration-residue check asked the same wrong question and was corrected with them