2026-03-07 02:59:07 -05:00
#!/usr/bin/env python3
2026-03-11 23:15:07 -04:00
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
2026-03-07 02:59:07 -05:00
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
import os
import re
2026-03-10 03:45:11 -04:00
ENV_VAR_FILE = " ./env_var.sh "
2026-03-07 02:59:07 -05:00
_current_lang = None
TRANSLATIONS = {
# Main menu
" Importation success! " : {
" fr " : " L ' importation est un succès! " ,
" en " : " Importation success! " ,
} ,
2026-03-12 05:31:45 -04:00
" Opening TODO ... " : {
2026-03-07 02:59:07 -05:00
" fr " : " Ouverture de TODO en cours ... " ,
" en " : " Opening TODO ... " ,
} ,
2026-03-12 05:31:45 -04:00
" => Enter your choice by number and press Enter! " : {
2026-03-07 02:59:07 -05:00
" fr " : " => Entre tes directives par son chiffre et fait Entrée! " ,
" en " : " => Enter your choice by number and press Enter! " ,
} ,
2026-03-12 05:31:45 -04:00
" Command: " : {
2026-03-07 02:59:07 -05:00
" fr " : " Commande : " ,
" en " : " Command: " ,
} ,
2026-03-12 05:31:45 -04:00
" Execute " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 🧰 Exécution " ,
" en " : " 🧰 Execute " ,
2026-03-07 02:59:07 -05:00
} ,
2026-03-12 05:31:45 -04:00
" Install " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 📦 Installation " ,
" en " : " 📦 Install " ,
2026-03-07 02:59:07 -05:00
} ,
2026-08-10 03:10:50 -04:00
" Assistant " : {
" fr " : " 🤖 Assistant " ,
" en " : " 🤖 Assistant " ,
} ,
2026-03-12 05:31:45 -04:00
" Fork - Open TODO in a new tab " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 🔀 Fork - Ouvre TODO dans une nouvelle tabulation " ,
" en " : " 🔀 Fork - Open TODO in a new tab " ,
2026-03-07 02:59:07 -05:00
} ,
2026-03-12 05:31:45 -04:00
" Quit " : {
2026-03-07 02:59:07 -05:00
" fr " : " Quitter " ,
" en " : " Quit " ,
} ,
2026-03-12 05:31:45 -04:00
" Command not found ! " : {
2026-03-07 02:59:07 -05:00
" fr " : " Commande non trouvée ! " ,
" en " : " Command not found ! " ,
} ,
2026-08-07 03:22:26 -04:00
" Back " : {
" fr " : " 🔙 Retour " ,
" en " : " 🔙 Back " ,
} ,
# Execute submenu
" Run - Execute and install an instance " : {
" fr " : " 🏃 Run - Exécuter et installer une instance " ,
" en " : " 🏃 Run - Execute and install an instance " ,
} ,
" Automation - Demonstration of developed features " : {
" fr " : " 🦾 Automatisation - Demonstration des fonctions développées " ,
" en " : " 🦾 Automation - Demonstration of developed features " ,
} ,
" Update - Update all developed staging source code " : {
" fr " : " 🔃 Mise à jour - Update all developed staging source code " ,
" en " : " 🔃 Update - Update all developed staging source code " ,
} ,
" Code - Developer tools " : {
" fr " : " 💻 Code - Outil pour développeur " ,
" en " : " 💻 Code - Developer tools " ,
} ,
" Doc - Documentation search " : {
" fr " : " 📖 Doc - Recherche de documentation " ,
" en " : " 📖 Doc - Documentation search " ,
} ,
" Database - Database tools " : {
" fr " : " 💾 Database - Outils sur les bases de données " ,
" en " : " 💾 Database - Database tools " ,
} ,
" Process - Execution tools " : {
" fr " : " 📟 Process - Outils sur les executions " ,
" en " : " 📟 Process - Execution tools " ,
} ,
" Config - Configuration file management " : {
" fr " : " 🔧 Config - Traitement du fichier de configuration " ,
" en " : " 🔧 Config - Configuration file management " ,
} ,
" Network - Network tools " : {
" fr " : " 📡 Réseau - Outil réseautique " ,
" en " : " 📡 Network - Network tools " ,
} ,
" Security - Dependency security audit " : {
" fr " : " 🔒 Sécurité - Audit de sécurité des dépendances " ,
" en " : " 🔒 Security - Dependency security audit " ,
} ,
2026-03-12 05:31:45 -04:00
" RTK - CLI proxy to reduce LLM token consumption " : {
2026-03-12 02:51:01 -04:00
" fr " : " RTK - Proxy CLI pour réduire la consommation de tokens LLM " ,
" en " : " RTK - CLI proxy to reduce LLM token consumption " ,
} ,
2026-08-07 03:22:26 -04:00
" Language - Change language / Changer la langue " : {
" fr " : " 🌍 Langue - Changer la langue / Change language " ,
" en " : " 🌍 Language - Change language / Changer la langue " ,
} ,
# Deploy section
" Deploy - Deploy ERPLibre locally " : {
" fr " : " 🚀 Déploiement - Déployer ERPLibre localement " ,
" en " : " 🚀 Deploy - Deploy ERPLibre locally " ,
} ,
2026-03-12 05:40:22 -04:00
" Deploy ERPLibre to a local directory! " : {
" fr " : " Déployer ERPLibre dans un répertoire local! " ,
" en " : " Deploy ERPLibre to a local directory! " ,
} ,
" Clone ERPLibre locally (git clone) " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 📥 Cloner ERPLibre localement (git clone) " ,
" en " : " 📥 Clone ERPLibre locally (git clone) " ,
2026-03-12 05:40:22 -04:00
} ,
" Target directory path (default: ~/erplibre): " : {
" fr " : " Chemin du répertoire cible (défaut: ~/erplibre) : " ,
" en " : " Target directory path (default: ~/erplibre): " ,
} ,
" Directory already exists: " : {
" fr " : " Le répertoire existe déjà : " ,
" en " : " Directory already exists: " ,
} ,
" Cloning ERPLibre... " : {
" fr " : " Clonage d ' ERPLibre en cours... " ,
" en " : " Cloning ERPLibre... " ,
} ,
" ERPLibre cloned successfully to: " : {
" fr " : " ERPLibre cloné avec succès dans : " ,
" en " : " ERPLibre cloned successfully to: " ,
} ,
" Error cloning ERPLibre: " : {
" fr " : " Erreur lors du clonage d ' ERPLibre : " ,
" en " : " Error cloning ERPLibre: " ,
} ,
2026-03-22 22:28:24 -04:00
" Configure sshfs " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 📁 Configurer sshfs " ,
" en " : " 📁 Configure sshfs " ,
} ,
" Local " : {
" fr " : " 💻 Local " ,
" en " : " 💻 Local " ,
} ,
" SSH (remote host) " : {
" fr " : " SSH (hôte distant) " ,
" en " : " SSH (remote host) " ,
} ,
" Remote & services " : {
" fr " : " 🌐 Distant & services " ,
" en " : " 🌐 Remote & services " ,
} ,
" SSH (remote host)... " : {
" fr " : " 🔐 SSH (hôte distant)… " ,
" en " : " 🔐 SSH (remote host)... " ,
} ,
" Deploy ERPLibre to a remote host over SSH! " : {
" fr " : " Déployer ERPLibre sur un hôte distant via SSH ! " ,
" en " : " Deploy ERPLibre to a remote host over SSH! " ,
} ,
" Virtualization & notifications " : {
" fr " : " Virtualisation & notifications " ,
" en " : " Virtualization & notifications " ,
} ,
" Development " : {
" fr " : " 🧰 Développement " ,
" en " : " 🧰 Development " ,
} ,
" Data " : {
" fr " : " 📊 Données " ,
" en " : " 📊 Data " ,
} ,
" Sources & documentation " : {
" fr " : " 📚 Sources & documentation " ,
" en " : " 📚 Sources & documentation " ,
} ,
" AI & automation " : {
" fr " : " 🧠 IA & automatisation " ,
" en " : " 🧠 AI & automation " ,
} ,
" Deployment, network & security " : {
" fr " : " 🌐 Déploiement, réseau & sécurité " ,
" en " : " 🌐 Deployment, network & security " ,
} ,
" Preferences " : {
" fr " : " 🎨 Préférences " ,
" en " : " 🎨 Preferences " ,
} ,
" Deployment " : {
" fr " : " 🚀 Déploiement " ,
" en " : " 🚀 Deployment " ,
} ,
" Manage " : {
" fr " : " 🛠 Gérer " ,
" en " : " 🛠 Manage " ,
} ,
" Catalog " : {
" fr " : " 📚 Catalogue " ,
" en " : " 📚 Catalog " ,
} ,
" Open the console on a VM " : {
" fr " : " 🖥 Ouvrir la console d ' une VM " ,
" en " : " 🖥 Open the console on a VM " ,
} ,
" Backup " : {
" fr " : " Sauvegarde " ,
" en " : " Backup " ,
} ,
" Restore " : {
" fr " : " Restauration " ,
" en " : " Restore " ,
} ,
" Danger zone " : {
" fr " : " Zone dangereuse " ,
" en " : " Danger zone " ,
} ,
" Setup " : {
" fr " : " Installation " ,
" en " : " Setup " ,
} ,
" Status " : {
" fr " : " Statut " ,
" en " : " Status " ,
} ,
" Optimize " : {
" fr " : " Optimisation " ,
" en " : " Optimize " ,
} ,
" Generate " : {
" fr " : " Génération " ,
" en " : " Generate " ,
} ,
" Advanced " : {
" fr " : " Avancé " ,
" en " : " Advanced " ,
} ,
" To leave the console, press Ctrl+] (then Enter). " : {
" fr " : " Pour quitter la console, appuyez sur Ctrl+] (puis Entrée). " ,
" en " : " To leave the console, press Ctrl+] (then Enter). " ,
} ,
" Default login (if set at deploy): erplibre / erplibre " : {
" fr " : " Login par défaut (si défini au déploiement) : "
" erplibre / erplibre " ,
" en " : " Default login (if set at deploy): erplibre / erplibre " ,
} ,
" Console password for ' erplibre ' (default: erplibre): " : {
" fr " : " Mot de passe console pour « erplibre » (défaut : erplibre) : " ,
" en " : " Console password for ' erplibre ' (default: erplibre): " ,
} ,
" Console/SSH login: " : {
" fr " : " Connexion console/SSH : " ,
" en " : " Console/SSH login: " ,
} ,
" Default login: " : {
" fr " : " Login par défaut : " ,
" en " : " Default login: " ,
} ,
" Add this VM to ~/.ssh/config? (y/N): " : {
" fr " : " Ajouter cette VM à ~/.ssh/config ? (o/N, défaut : non) : " ,
" en " : " Add this VM to ~/.ssh/config? (y/N, default: no): " ,
} ,
" Add each VM to ~/.ssh/config? (Y/n): " : {
" fr " : " Ajouter chaque VM à ~/.ssh/config ? (O/n, défaut : oui) : " ,
" en " : " Add each VM to ~/.ssh/config? (Y/n, default: yes): " ,
} ,
" Waiting for the VM IP (DHCP lease)... " : {
" fr " : " Attente de l ' IP de la VM (bail DHCP)... " ,
" en " : " Waiting for the VM IP (DHCP lease)... " ,
} ,
" No IP yet; add it later once the VM has booted. " : {
" fr " : " Pas encore d ' IP ; à ajouter plus tard une fois la VM démarrée. " ,
" en " : " No IP yet; add it later once the VM has booted. " ,
} ,
" Added to ~/.ssh/config: " : {
" fr " : " Ajouté à ~/.ssh/config : " ,
" en " : " Added to ~/.ssh/config: " ,
2026-03-22 22:28:24 -04:00
} ,
" SSH address input method " : {
" fr " : " Méthode de saisie de l ' adresse SSH " ,
" en " : " SSH address input method " ,
} ,
" Manual entry " : {
" fr " : " Saisie manuelle " ,
" en " : " Manual entry " ,
} ,
" From ~/.ssh/config " : {
" fr " : " Depuis ~/.ssh/config " ,
" en " : " From ~/.ssh/config " ,
} ,
" Your choice (1/2): " : {
" fr " : " Votre choix (1/2) : " ,
" en " : " Your choice (1/2): " ,
} ,
" No SSH hosts found in ~/.ssh/config " : {
" fr " : " Aucun hôte SSH trouvé dans ~/.ssh/config " ,
" en " : " No SSH hosts found in ~/.ssh/config " ,
} ,
" Select SSH host number: " : {
" fr " : " Numéro de l ' hôte SSH à sélectionner : " ,
" en " : " Select SSH host number: " ,
} ,
" Invalid selection! " : {
" fr " : " Sélection invalide! " ,
" en " : " Invalid selection! " ,
} ,
" SSH host (e.g.: user@192.168.1.100): " : {
" fr " : " Hôte SSH (ex: user@192.168.1.100) : " ,
" en " : " SSH host (e.g.: user@192.168.1.100): " ,
} ,
" Mounting sshfs on: " : {
" fr " : " Montage sshfs sur : " ,
" en " : " Mounting sshfs on: " ,
} ,
" Mounted on: " : {
" fr " : " Monté sur : " ,
" en " : " Mounted on: " ,
} ,
" To unmount: " : {
" fr " : " Pour démonter : " ,
" en " : " To unmount: " ,
} ,
" Error mounting sshfs: " : {
" fr " : " Erreur lors du montage sshfs : " ,
" en " : " Error mounting sshfs: " ,
} ,
2026-03-12 02:51:01 -04:00
# RTK (Rust Token Killer)
2026-03-12 05:31:45 -04:00
" Manage RTK (Rust Token Killer) for token optimization! " : {
2026-03-12 02:51:01 -04:00
" fr " : " Gérer RTK (Rust Token Killer) pour optimiser les tokens! " ,
" en " : " Manage RTK (Rust Token Killer) for token optimization! " ,
} ,
2026-03-12 05:31:45 -04:00
" Install RTK " : {
2026-03-12 02:51:01 -04:00
" fr " : " Installer RTK " ,
" en " : " Install RTK " ,
} ,
2026-03-12 05:31:45 -04:00
" Check RTK version " : {
2026-03-12 02:51:01 -04:00
" fr " : " Vérifier la version de RTK " ,
" en " : " Check RTK version " ,
} ,
2026-03-12 05:31:45 -04:00
" Show cumulative token savings " : {
2026-03-12 02:51:01 -04:00
" fr " : " Afficher les économies de tokens cumulées " ,
" en " : " Show cumulative token savings " ,
} ,
2026-03-12 05:31:45 -04:00
" Discover optimization opportunities " : {
2026-03-12 02:51:01 -04:00
" fr " : " Identifier les opportunités d ' optimisation " ,
" en " : " Discover optimization opportunities " ,
} ,
2026-03-12 05:31:45 -04:00
" Initialize global auto-rewrite hook " : {
2026-03-12 02:51:01 -04:00
" fr " : " Initialiser le hook auto-rewrite global " ,
" en " : " Initialize global auto-rewrite hook " ,
} ,
2026-03-12 05:31:45 -04:00
" Check RTK status " : {
2026-03-12 02:51:01 -04:00
" fr " : " Vérifier le statut de RTK " ,
" en " : " Check RTK status " ,
} ,
2026-03-12 05:31:45 -04:00
" RTK is not installed. Use option 1 to install it. " : {
2026-03-12 02:51:01 -04:00
" fr " : " RTK n ' est pas installé. Utilisez l ' option 1 pour l ' installer. " ,
" en " : " RTK is not installed. Use option 1 to install it. " ,
} ,
2026-03-12 05:31:45 -04:00
" RTK is installed, version: " : {
2026-03-12 02:51:01 -04:00
" fr " : " RTK est installé, version : " ,
" en " : " RTK is installed, version: " ,
} ,
2026-03-12 05:31:45 -04:00
" Global auto-rewrite hook: active " : {
2026-03-12 02:51:01 -04:00
" fr " : " Hook auto-rewrite global : actif " ,
" en " : " Global auto-rewrite hook: active " ,
} ,
2026-03-12 05:31:45 -04:00
" Global auto-rewrite hook: inactive " : {
2026-03-12 02:51:01 -04:00
" fr " : " Hook auto-rewrite global : inactif " ,
" en " : " Global auto-rewrite hook: inactive " ,
} ,
2026-03-12 05:31:45 -04:00
" Installation method: " : {
2026-03-12 02:51:01 -04:00
" fr " : " Méthode d ' installation : " ,
" en " : " Installation method: " ,
} ,
2026-03-12 05:31:45 -04:00
" curl - Automatic install script " : {
2026-03-12 02:51:01 -04:00
" fr " : " curl - Script d ' installation automatique " ,
" en " : " curl - Automatic install script " ,
} ,
2026-03-12 05:31:45 -04:00
" brew - Homebrew (macOS/Linux) " : {
2026-03-12 02:51:01 -04:00
" fr " : " brew - Homebrew (macOS/Linux) " ,
" en " : " brew - Homebrew (macOS/Linux) " ,
} ,
2026-03-12 05:31:45 -04:00
" cargo - Build from source (Rust required) " : {
2026-03-12 02:51:01 -04:00
" fr " : " cargo - Compilation depuis les sources (Rust requis) " ,
" en " : " cargo - Build from source (Rust required) " ,
} ,
2026-03-07 02:59:07 -05:00
# Prompts and messages
2026-03-12 05:31:45 -04:00
" Enter your password: " : {
2026-03-07 02:59:07 -05:00
" fr " : " Entrez votre mot de passe : " ,
" en " : " Enter your password: " ,
} ,
2026-03-12 05:31:45 -04:00
" Write your question " : {
2026-03-07 02:59:07 -05:00
" fr " : " Écrit moi ta question " ,
" en " : " Write your question " ,
} ,
2026-03-12 05:31:45 -04:00
" Do you want a new instance? " : {
2026-03-07 02:59:07 -05:00
" fr " : " Voulez-vous une nouvelle instance? " ,
" en " : " Do you want a new instance? " ,
} ,
2026-03-12 05:31:45 -04:00
" SSH port-forwarding " : {
2026-03-07 02:59:07 -05:00
" fr " : " SSH port-forwarding " ,
" en " : " SSH port-forwarding " ,
} ,
2026-03-12 05:31:45 -04:00
" Network performance request per second " : {
2026-03-07 02:59:07 -05:00
" fr " : " Performance réseau en requêtes par seconde " ,
" en " : " Network performance request per second " ,
} ,
2026-03-12 05:31:45 -04:00
" Setup queue job for parallelism " : {
2026-03-07 02:59:07 -05:00
" fr " : " Configurer la file d ' attente pour l ' exécution parallèle " ,
" en " : " Setup queue job for parallelism " ,
} ,
2026-03-12 05:31:45 -04:00
" Choose your database " : {
2026-03-07 02:59:07 -05:00
" fr " : " Choisir sa base de données " ,
" en " : " Choose your database " ,
} ,
2026-03-12 05:31:45 -04:00
" Development update " : {
2026-03-07 02:59:07 -05:00
" fr " : " Mise à jour du développement " ,
" en " : " Development update " ,
} ,
2026-03-12 05:31:45 -04:00
" What do you need for development? " : {
2026-03-07 02:59:07 -05:00
" fr " : " Qu ' avez-vous de besoin pour développer? " ,
" en " : " What do you need for development? " ,
} ,
2026-03-12 05:31:45 -04:00
" Looking for documentation? " : {
2026-03-07 02:59:07 -05:00
" fr " : " Vous cherchez de la documentation? " ,
" en " : " Looking for documentation? " ,
} ,
2026-03-12 05:31:45 -04:00
" Make changes to databases! " : {
2026-03-07 02:59:07 -05:00
" fr " : " Faites des modifications sur les bases de données! " ,
" en " : " Make changes to databases! " ,
} ,
2026-06-25 02:25:40 -04:00
# Database drop / erase
" Erase a database " : {
" fr " : " Effacer une base de données " ,
" en " : " Erase a database " ,
} ,
" Erase a database — irreversible operation! " : {
" fr " : " Effacer une base de données — opération irréversible ! " ,
" en " : " Erase a database — irreversible operation! " ,
} ,
" Erase ALL databases (make db_drop_all) " : {
" fr " : " Effacer TOUTES les bases de données (make db_drop_all) " ,
" en " : " Erase ALL databases (make db_drop_all) " ,
} ,
" Erase a single database " : {
" fr " : " Effacer une seule base de données " ,
" en " : " Erase a single database " ,
} ,
" You are about to erase ALL databases. This cannot be undone. " : {
" fr " : (
" Tu es sur le point d ' effacer TOUTES les bases de données. "
" C ' est irréversible. "
) ,
" en " : " You are about to erase ALL databases. This cannot be undone. " ,
} ,
" You are about to erase the database ' {database} ' . This cannot be undone. " : {
" fr " : (
" Tu es sur le point d ' effacer la base de données « {database} ». "
" C ' est irréversible. "
) ,
" en " : (
" You are about to erase the database ' {database} ' . "
" This cannot be undone. "
) ,
} ,
" Type ' oui ' to confirm (default: no): " : {
" fr " : " Tape « oui » pour confirmer (défaut : non) : " ,
" en " : " Type ' oui ' / ' yes ' to confirm (default: no): " ,
} ,
" Database deletion cancelled. " : {
" fr " : " Effacement de la base de données annulé. " ,
" en " : " Database deletion cancelled. " ,
} ,
" No database selected. " : {
" fr " : " Aucune base de données sélectionnée. " ,
" en " : " No database selected. " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" Cannot list the databases (exit code): " : {
" fr " : " Impossible de lister les bases (code de retour) : " ,
" en " : " Cannot list the databases (exit code): " ,
} ,
" Is PostgreSQL running? " : {
" fr " : " PostgreSQL est-il démarré ? " ,
" en " : " Is PostgreSQL running? " ,
} ,
" No database on this PostgreSQL server. " : {
" fr " : " Aucune base sur ce serveur PostgreSQL. " ,
" en " : " No database on this PostgreSQL server. " ,
} ,
2026-03-12 05:31:45 -04:00
" Manage execution processes! " : {
2026-03-07 02:59:07 -05:00
" fr " : " Manipuler les processus d ' exécution! " ,
" en " : " Manage execution processes! " ,
} ,
2026-03-12 05:31:45 -04:00
" Manage ERPLibre and Odoo configuration! " : {
2026-03-07 02:59:07 -05:00
" fr " : " Manipuler la configuration ERPLibre et Odoo! " ,
" en " : " Manage ERPLibre and Odoo configuration! " ,
} ,
2026-03-12 05:31:45 -04:00
" Network tools! " : {
2026-03-07 02:59:07 -05:00
" fr " : " Outil réseautique! " ,
" en " : " Network tools! " ,
} ,
2026-03-12 05:31:45 -04:00
" Dependency security audit! " : {
2026-03-07 02:59:07 -05:00
" fr " : " Audit de securite des dépendances! " ,
" en " : " Dependency security audit! " ,
} ,
2026-03-12 05:31:45 -04:00
" The Bash script failed with return code " : {
2026-03-07 02:59:07 -05:00
" fr " : " Le script Bash a échoué avec le code de retour " ,
" en " : " The Bash script failed with return code " ,
} ,
2026-03-12 05:31:45 -04:00
" No installed environment found. Install an Odoo version first. " : {
2026-03-07 02:59:07 -05:00
" fr " : " Aucun environnement installe trouve. Installez d ' abord une version d ' Odoo. " ,
" en " : " No installed environment found. Install an Odoo version first. " ,
} ,
2026-03-12 05:31:45 -04:00
" Choose an environment for the audit: " : {
2026-03-07 02:59:07 -05:00
" fr " : " Choisir un environnement pour l ' audit : " ,
" en " : " Choose an environment for the audit: " ,
} ,
2026-03-12 05:31:45 -04:00
" Select: " : {
2026-03-07 02:59:07 -05:00
" fr " : " Sélection : " ,
" en " : " Select: " ,
} ,
2026-03-12 05:31:45 -04:00
" Error, cannot understand value " : {
2026-03-07 02:59:07 -05:00
" fr " : " Erreur, impossible de comprendre la valeur " ,
" en " : " Error, cannot understand value " ,
} ,
2026-03-12 05:31:45 -04:00
" Dependencies file not found: " : {
2026-03-07 02:59:07 -05:00
" fr " : " Fichier de dépendances introuvable : " ,
" en " : " Dependencies file not found: " ,
} ,
2026-03-12 05:31:45 -04:00
" Execution: " : {
2026-03-07 02:59:07 -05:00
" fr " : " Execution : " ,
" en " : " Execution: " ,
} ,
2026-03-12 05:31:45 -04:00
" Current " : {
2026-03-07 02:59:07 -05:00
" fr " : " Actuel " ,
" en " : " Current " ,
} ,
2026-03-12 05:31:45 -04:00
" Default " : {
2026-03-07 02:59:07 -05:00
" fr " : " Défaut " ,
" en " : " Default " ,
} ,
2026-03-12 05:31:45 -04:00
" Reboot TODO ... " : {
2026-03-07 02:59:07 -05:00
" fr " : " Reboot TODO ... " ,
" en " : " Reboot TODO ... " ,
} ,
2026-03-12 05:31:45 -04:00
" pip-audit - Check vulnerabilities on Python environments " : {
2026-03-07 02:59:07 -05:00
" fr " : " pip-audit - Verifier les vulnérabilités sur les environnements Python " ,
" en " : " pip-audit - Check vulnerabilities on Python environments " ,
} ,
2026-03-12 05:31:45 -04:00
" Will execute: " : {
2026-03-07 02:59:07 -05:00
" fr " : " Va exécuter : " ,
" en " : " Will execute: " ,
} ,
2026-03-12 05:31:45 -04:00
" Choose a version: " : {
2026-03-07 02:59:07 -05:00
" fr " : " Choisir une version : " ,
" en " : " Choose a version: " ,
} ,
2026-03-30 14:46:56 -04:00
" Install type: " : {
" fr " : " Type d ' installation : " ,
" en " : " Install type: " ,
} ,
" Standard install (without extra modules) " : {
" fr " : " Installation standard (sans modules extra) " ,
" en " : " Standard install (without extra modules) " ,
} ,
" Install with extra modules (CybroOdoo - large, slow) " : {
" fr " : " Installation avec modules extra (CybroOdoo - gros, lent) " ,
" en " : " Install with extra modules (CybroOdoo - large, slow) " ,
} ,
2026-03-07 02:59:07 -05:00
# todo.json translatable prompt_descriptions
2026-03-12 05:31:45 -04:00
" Test - Minimal base instance " : {
2026-03-07 02:59:07 -05:00
" fr " : " Test - Instance de base minimale " ,
" en " : " Test - Minimal base instance " ,
} ,
2026-03-12 05:31:45 -04:00
" Open RobotLibre 🤖 minimal " : {
2026-03-07 02:59:07 -05:00
" fr " : " Ouvrir RobotLibre 🤖 minimal " ,
" en " : " Open RobotLibre 🤖 minimal " ,
} ,
2026-03-12 05:31:45 -04:00
" Open RobotLibre 🤖 with search enabled " : {
2026-03-07 02:59:07 -05:00
" fr " : " Ouvrir RobotLibre 🤖 en activant la recherche " ,
" en " : " Open RobotLibre 🤖 with search enabled " ,
} ,
2026-03-12 05:31:45 -04:00
" Open ERPLibre with TODO 🤖 " : {
2026-03-07 02:59:07 -05:00
" fr " : " Ouvrir ERPLibre avec TODO 🤖 " ,
" en " : " Open ERPLibre with TODO 🤖 " ,
} ,
2026-08-07 03:22:26 -04:00
" Update all erplibre_base on database test " : {
" fr " : " 🔄 Mise à jour de tous les erplibre_base sur la base de données test " ,
" en " : " 🔄 Update all erplibre_base on database test " ,
} ,
" Show code status " : {
" fr " : " 🔍 Afficher le statut du code " ,
" en " : " 🔍 Show code status " ,
} ,
" Stash all code " : {
" fr " : " 📦 Remiser tout le code " ,
" en " : " 📦 Stash all code " ,
} ,
" Format modified code " : {
" fr " : " 🎨 Formater le code modifié " ,
" en " : " 🎨 Format modified code " ,
} ,
2026-03-07 02:59:07 -05:00
# todo.py hardcoded prompt_descriptions
2026-03-12 05:31:45 -04:00
" Mobile - Compile and run software " : {
2026-03-07 02:59:07 -05:00
" fr " : " Mobile - Compiler et exécuter le logiciel " ,
" en " : " Mobile - Compile and run software " ,
} ,
2026-08-07 03:22:26 -04:00
" Upgrade Odoo - Migration Database " : {
" fr " : " 🚚 Mise à jour Odoo - Migration de base de données " ,
" en " : " 🚚 Upgrade Odoo - Migration Database " ,
} ,
" Upgrade Poetry - Dependency of Odoo " : {
" fr " : " 📦 Mise à jour Poetry - Dépendances d ' Odoo " ,
" en " : " 📦 Upgrade Poetry - Dependency of Odoo " ,
} ,
" Open SHELL " : {
" fr " : " 🐚 Ouvrir le SHELL " ,
" en " : " 🐚 Open SHELL " ,
} ,
" Upgrade Module " : {
" fr " : " 🧩 Mise à jour de module " ,
" en " : " 🧩 Upgrade Module " ,
} ,
" Debug " : {
" fr " : " 🐛 Débogage " ,
" en " : " 🐛 Debug " ,
} ,
2026-03-12 05:31:45 -04:00
" Migration module coverage " : {
2026-03-07 02:59:07 -05:00
" fr " : " Couverture de migration des modules " ,
" en " : " Migration module coverage " ,
} ,
2026-03-12 05:31:45 -04:00
" What change between version " : {
2026-03-07 02:59:07 -05:00
" fr " : " Quels changements entre les versions " ,
" en " : " What change between version " ,
} ,
2026-03-12 05:31:45 -04:00
" OCA guidelines " : {
2026-03-07 02:59:07 -05:00
" fr " : " Directives OCA " ,
" en " : " OCA guidelines " ,
} ,
2026-03-12 05:31:45 -04:00
" OCA migration Odoo 19 milestone " : {
2026-03-07 02:59:07 -05:00
" fr " : " Migration OCA Odoo 19 - Jalons " ,
" en " : " OCA migration Odoo 19 milestone " ,
} ,
2026-03-12 05:31:45 -04:00
" Download database to create backup (.zip) " : {
2026-03-07 02:59:07 -05:00
" fr " : " Télécharger une base de données pour créer une sauvegarde (.zip) " ,
" en " : " Download database to create backup (.zip) " ,
} ,
2026-03-12 05:31:45 -04:00
" Restore from backup (.zip) " : {
2026-03-07 02:59:07 -05:00
" fr " : " Restaurer a partir d ' une sauvegarde (.zip) " ,
" en " : " Restore from backup (.zip) " ,
} ,
2026-03-12 05:31:45 -04:00
" Create backup (.zip) " : {
2026-03-07 02:59:07 -05:00
" fr " : " Créer une sauvegarde (.zip) " ,
" en " : " Create backup (.zip) " ,
} ,
2026-03-12 05:31:45 -04:00
" Kill Odoo process from actual port " : {
2026-03-09 16:34:43 -04:00
" fr " : " Terminer le processus Odoo du port actuel " ,
" en " : " Kill Odoo process from actual port " ,
} ,
2026-03-12 05:31:45 -04:00
" Kill git daemon server process " : {
2026-03-09 16:34:43 -04:00
" fr " : " Terminer le processus du serveur git daemon " ,
" en " : " Kill git daemon server process " ,
} ,
2026-03-12 05:31:45 -04:00
" Git daemon process killed. " : {
2026-03-09 16:34:43 -04:00
" fr " : " Processus git daemon terminé. " ,
" en " : " Git daemon process killed. " ,
2026-03-07 02:59:07 -05:00
} ,
2026-03-12 05:31:45 -04:00
" Generate all configuration " : {
2026-03-07 02:59:07 -05:00
" fr " : " Générer toute la configuration " ,
" en " : " Generate all configuration " ,
} ,
2026-03-12 05:31:45 -04:00
" Generate from pre-configuration " : {
2026-03-07 02:59:07 -05:00
" fr " : " Générer a partir de la pre-configuration " ,
" en " : " Generate from pre-configuration " ,
} ,
2026-03-12 05:31:45 -04:00
" Generate from backup file " : {
2026-03-07 02:59:07 -05:00
" fr " : " Générer a partir d ' un fichier de sauvegarde " ,
" en " : " Generate from backup file " ,
} ,
2026-03-12 05:31:45 -04:00
" Generate from database " : {
2026-03-07 02:59:07 -05:00
" fr " : " Générer a partir de la base de données " ,
" en " : " Generate from database " ,
} ,
2026-03-12 05:31:45 -04:00
" base " : {
2026-03-07 02:59:07 -05:00
" fr " : " base " ,
" en " : " base " ,
} ,
2026-03-12 05:31:45 -04:00
" base + code_generator " : {
2026-03-07 02:59:07 -05:00
" fr " : " base + code_generator " ,
" en " : " base + code_generator " ,
} ,
2026-03-12 05:31:45 -04:00
" base + image_db " : {
2026-03-07 02:59:07 -05:00
" fr " : " base + image_db " ,
" en " : " base + image_db " ,
} ,
2026-03-12 05:31:45 -04:00
" all " : {
2026-03-07 02:59:07 -05:00
" fr " : " tout " ,
" en " : " all " ,
} ,
2026-03-12 05:31:45 -04:00
" Debug todo.py " : {
2026-03-07 02:59:07 -05:00
" fr " : " Débogage todo.py " ,
" en " : " Debug todo.py " ,
} ,
2026-08-07 03:22:26 -04:00
# Test section
" Test - Test an Odoo module " : {
" fr " : " 🧪 Test - Tester un module Odoo " ,
" en " : " 🧪 Test - Test an Odoo module " ,
} ,
2026-03-12 05:31:45 -04:00
" Test an Odoo module on a temporary database! " : {
2026-03-08 15:53:51 -04:00
" fr " : " Tester un module Odoo sur une base de données temporaire! " ,
" en " : " Test an Odoo module on a temporary database! " ,
} ,
2026-03-12 05:31:45 -04:00
" Test a module " : {
2026-03-08 15:53:51 -04:00
" fr " : " Tester un module " ,
" en " : " Test a module " ,
} ,
2026-03-12 05:31:45 -04:00
" Test a module with code coverage " : {
2026-03-08 15:53:51 -04:00
" fr " : " Tester un module avec couverture de code " ,
" en " : " Test a module with code coverage " ,
} ,
2026-03-12 05:31:45 -04:00
" ERPLibre unit tests " : {
2026-03-10 01:44:10 -04:00
" fr " : " Tests unitaires ERPLibre " ,
" en " : " ERPLibre unit tests " ,
} ,
2026-08-10 03:10:50 -04:00
" Mail unit tests " : {
" fr " : " Tests unitaires courriel " ,
" en " : " Mail unit tests " ,
} ,
" Analyse unit tests " : {
" fr " : " Tests unitaires analyse " ,
" en " : " Analyse unit tests " ,
} ,
2026-03-12 05:31:45 -04:00
" Running unit tests " : {
2026-03-10 01:44:10 -04:00
" fr " : " Exécution des tests unitaires " ,
" en " : " Running unit tests " ,
} ,
2026-03-12 05:31:45 -04:00
" All unit tests passed " : {
2026-03-10 01:44:10 -04:00
" fr " : " Tous les tests unitaires ont réussi " ,
" en " : " All unit tests passed " ,
} ,
2026-03-12 05:31:45 -04:00
" Some unit tests failed, exit code " : {
2026-03-10 01:44:10 -04:00
" fr " : " Des tests unitaires ont échoué, code de sortie " ,
" en " : " Some unit tests failed, exit code " ,
} ,
2026-03-12 05:31:45 -04:00
" Module name to test: " : {
2026-03-08 15:53:51 -04:00
" fr " : " Nom du module à tester : " ,
" en " : " Module name to test: " ,
} ,
2026-03-12 05:31:45 -04:00
" Temporary database name (default: test_todo_tmp): " : {
2026-03-08 15:53:51 -04:00
" fr " : " Nom de la base de données temporaire (défaut: test_todo_tmp) : " ,
" en " : " Temporary database name (default: test_todo_tmp): " ,
} ,
2026-03-12 05:31:45 -04:00
" Extra modules to install (comma-separated, empty for none): " : {
2026-03-08 15:53:51 -04:00
" fr " : " Modules supplémentaires à installer (séparés par des virgules, vide pour aucun) : " ,
" en " : " Extra modules to install (comma-separated, empty for none): " ,
} ,
2026-03-12 05:31:45 -04:00
" Log level (default: test): " : {
2026-03-08 15:53:51 -04:00
" fr " : " Niveau de log (défaut: test) : " ,
" en " : " Log level (default: test): " ,
} ,
2026-03-12 05:31:45 -04:00
" Creating temporary database " : {
2026-03-08 15:53:51 -04:00
" fr " : " Création de la base de données temporaire " ,
" en " : " Creating temporary database " ,
} ,
2026-03-12 05:31:45 -04:00
" Installing modules " : {
2026-03-08 15:53:51 -04:00
" fr " : " Installation des modules " ,
" en " : " Installing modules " ,
} ,
2026-03-12 05:31:45 -04:00
" Running tests " : {
2026-03-08 15:53:51 -04:00
" fr " : " Exécution des tests " ,
" en " : " Running tests " ,
} ,
2026-03-12 05:31:45 -04:00
" Cleaning up temporary database " : {
2026-03-08 15:53:51 -04:00
" fr " : " Suppression de la base de données temporaire " ,
" en " : " Cleaning up temporary database " ,
} ,
2026-08-07 03:22:26 -04:00
" Keep the temporary database? (y/N): " : {
" fr " : " Conserver la base de données temporaire? (o/N, défaut : non) : " ,
" en " : " Keep the temporary database? (y/N, default: no): " ,
} ,
2026-03-12 05:31:45 -04:00
" Database kept " : {
2026-03-08 15:53:51 -04:00
" fr " : " Base de données conservée " ,
" en " : " Database kept " ,
} ,
2026-03-12 05:31:45 -04:00
" Tests completed successfully! " : {
2026-03-08 15:53:51 -04:00
" fr " : " Tests terminés avec succès! " ,
" en " : " Tests completed successfully! " ,
} ,
2026-03-12 05:31:45 -04:00
" Tests failed with return code " : {
2026-03-08 15:53:51 -04:00
" fr " : " Les tests ont échoué avec le code de retour " ,
" en " : " Tests failed with return code " ,
} ,
2026-03-12 05:31:45 -04:00
" Module name is required! " : {
2026-03-08 15:53:51 -04:00
" fr " : " Le nom du module est requis! " ,
" en " : " Module name is required! " ,
} ,
2026-08-07 03:22:26 -04:00
# Git section
" Git - Git tools " : {
" fr " : " 🌿 Git - Outils Git " ,
" en " : " 🌿 Git - Git tools " ,
} ,
2026-03-12 05:31:45 -04:00
" Git management tools! " : {
2026-03-09 16:34:43 -04:00
" fr " : " Outils de gestion Git! " ,
" en " : " Git management tools! " ,
} ,
2026-03-12 05:31:45 -04:00
" Local git server " : {
2026-03-09 16:34:43 -04:00
" fr " : " Serveur git local " ,
" en " : " Local git server " ,
} ,
2026-03-12 05:31:45 -04:00
" Manage local git repository server! " : {
2026-03-09 16:34:43 -04:00
" fr " : " Gérer le serveur de dépôts git local! " ,
" en " : " Manage local git repository server! " ,
} ,
2026-03-12 05:31:45 -04:00
" Deploy a local git server (~/.git-server) " : {
2026-03-09 16:34:43 -04:00
" fr " : " Déployer un serveur git local (~/.git-server) " ,
" en " : " Deploy a local git server (~/.git-server) " ,
} ,
2026-03-12 05:31:45 -04:00
" Deploy a production git server (/srv/git, root required) " : {
2026-03-09 16:34:43 -04:00
" fr " : " Déployer un serveur git production (/srv/git, root requis) " ,
" en " : " Deploy a production git server (/srv/git, root required) " ,
} ,
2026-03-12 05:31:45 -04:00
" Starting git server deployment... " : {
2026-03-09 16:34:43 -04:00
" fr " : " Démarrage du déploiement du serveur git... " ,
" en " : " Starting git server deployment... " ,
} ,
2026-03-12 05:31:45 -04:00
" Local mode (~/.git-server) " : {
2026-03-09 16:34:43 -04:00
" fr " : " Mode local (~/.git-server) " ,
" en " : " Local mode (~/.git-server) " ,
} ,
2026-03-12 05:31:45 -04:00
" Production mode (/srv/git, root required) " : {
2026-03-09 16:34:43 -04:00
" fr " : " Mode production (/srv/git, root requis) " ,
" en " : " Production mode (/srv/git, root required) " ,
} ,
2026-03-12 05:31:45 -04:00
" Run all (init + remote + push + serve) " : {
2026-03-09 16:34:43 -04:00
" fr " : " Tout exécuter (init + remote + push + serve) " ,
" en " : " Run all (init + remote + push + serve) " ,
} ,
2026-03-12 05:31:45 -04:00
" Init - Create bare repos " : {
2026-03-09 16:34:43 -04:00
" fr " : " Init - Créer les bare repos " ,
" en " : " Init - Create bare repos " ,
} ,
2026-03-12 05:31:45 -04:00
" Remote - Add local remotes " : {
2026-03-09 16:34:43 -04:00
" fr " : " Remote - Ajouter les remotes locaux " ,
" en " : " Remote - Add local remotes " ,
} ,
2026-03-12 05:31:45 -04:00
" Push - Push to local server " : {
2026-03-09 16:34:43 -04:00
" fr " : " Push - Pousser vers le serveur local " ,
" en " : " Push - Push to local server " ,
} ,
2026-03-12 05:31:45 -04:00
" Serve - Start git daemon " : {
2026-03-09 16:34:43 -04:00
" fr " : " Serve - Démarrer le daemon git " ,
" en " : " Serve - Start git daemon " ,
} ,
2026-03-12 04:04:52 -04:00
# Git remote add
2026-03-12 05:31:45 -04:00
" Add a remote to a local repository " : {
2026-03-12 04:04:52 -04:00
" fr " : " Ajouter un remote vers un dépôt local " ,
" en " : " Add a remote to a local repository " ,
} ,
2026-03-12 05:31:45 -04:00
" Remote name (default: localhost): " : {
2026-03-12 04:04:52 -04:00
" fr " : " Nom du remote (défaut: localhost) : " ,
" en " : " Remote name (default: localhost): " ,
} ,
2026-03-12 05:31:45 -04:00
" Repository address (e.g.: git://192.168.1.100/my-repo.git): " : {
2026-03-12 04:04:52 -04:00
" fr " : " Adresse du dépôt (ex: git://192.168.1.100/mon-repo.git) : " ,
" en " : " Repository address (e.g.: git://192.168.1.100/my-repo.git): " ,
} ,
2026-03-12 05:31:45 -04:00
" Repository address is required! " : {
2026-03-12 04:04:52 -04:00
" fr " : " L ' adresse du dépôt est requise! " ,
" en " : " Repository address is required! " ,
} ,
2026-03-12 05:31:45 -04:00
" Remote added successfully! " : {
2026-03-12 04:04:52 -04:00
" fr " : " Remote ajouté avec succès! " ,
" en " : " Remote added successfully! " ,
} ,
2026-03-12 05:31:45 -04:00
" Error adding remote: " : {
2026-03-12 04:04:52 -04:00
" fr " : " Erreur lors de l ' ajout du remote : " ,
" en " : " Error adding remote: " ,
} ,
# Git config vim
2026-03-12 05:31:45 -04:00
" Configure git local editor to vim " : {
2026-03-12 04:04:52 -04:00
" fr " : " Configuration git local par vim " ,
" en " : " Configure git local editor to vim " ,
} ,
2026-03-22 22:21:16 -04:00
" Generate git patch to /tmp " : {
" fr " : " Générer une patch git dans /tmp " ,
" en " : " Generate git patch to /tmp " ,
} ,
2026-03-12 05:31:45 -04:00
" Git editor configured to vim successfully! " : {
2026-03-12 04:04:52 -04:00
" fr " : " Éditeur git configuré sur vim avec succès! " ,
" en " : " Git editor configured to vim successfully! " ,
} ,
2026-03-12 05:31:45 -04:00
" Error during configuration: " : {
2026-03-12 04:04:52 -04:00
" fr " : " Erreur lors de la configuration : " ,
" en " : " Error during configuration: " ,
} ,
# GPT code - Claude automation
2026-03-12 05:31:45 -04:00
" Add an automation with Claude in todo.py " : {
2026-03-12 04:04:52 -04:00
" fr " : " Ajouter une automatisation avec Claude dans todo.py " ,
" en " : " Add an automation with Claude in todo.py " ,
} ,
2026-03-12 05:31:45 -04:00
" Description of the command to add: " : {
2026-03-12 04:04:52 -04:00
" fr " : " Description de la commande à ajouter : " ,
" en " : " Description of the command to add: " ,
} ,
2026-03-12 05:31:45 -04:00
" Bash command to execute: " : {
2026-03-12 04:04:52 -04:00
" fr " : " Commande bash à exécuter : " ,
" en " : " Bash command to execute: " ,
} ,
2026-03-12 05:31:45 -04:00
" Menu section (git/code/config/network/process): " : {
2026-03-12 04:04:52 -04:00
" fr " : " Section du menu (git/code/config/network/process) : " ,
" en " : " Menu section (git/code/config/network/process): " ,
} ,
2026-03-12 05:31:45 -04:00
" Automation added successfully in todo.json! " : {
2026-03-12 04:04:52 -04:00
" fr " : " Automatisation ajoutée avec succès dans todo.json! " ,
" en " : " Automation added successfully in todo.json! " ,
} ,
2026-03-12 05:31:45 -04:00
" Error adding automation: " : {
2026-03-12 04:04:52 -04:00
" fr " : " Erreur lors de l ' ajout de l ' automatisation : " ,
" en " : " Error adding automation: " ,
} ,
[ADD] make: add SSH remote deployment targets and todo.py integration
Enable deploying and managing ERPLibre on remote servers via SSH
directly from make and the interactive todo.py CLI, since only
local deployment was previously supported.
- New conf/make.ssh.Makefile with 11 targets: ssh_check, ssh_push,
ssh_install, ssh_run, ssh_stop, ssh_restart, ssh_status, ssh_logs,
ssh_make, ssh_install_systemd, ssh_install_nginx
- Variables: SSH_HOST (required), SSH_USER, SSH_PORT, SSH_KEY,
SSH_PATH, SSH_TARGET, SSH_DOMAIN, SSH_ADMIN_EMAIL
- Execute > Deploy menu extended with 11 SSH options in todo.py
- All strings translated fr/en in todo_i18n.py
Generated by Claude Code 2.1.101 model claude-sonnet-4-6
Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-04-10 22:53:07 -04:00
# SSH Deploy section
" Deploy - Deploy ERPLibre via SSH " : {
" fr " : " Déploiement - Déployer ERPLibre via SSH " ,
" en " : " Deploy - Deploy ERPLibre via SSH " ,
} ,
" SSH deployment tools! " : {
" fr " : " Outils de déploiement SSH! " ,
" en " : " SSH deployment tools! " ,
} ,
" SSH - Check connection " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 🔌 SSH - Vérifier la connexion " ,
" en " : " 🔌 SSH - Check connection " ,
[ADD] make: add SSH remote deployment targets and todo.py integration
Enable deploying and managing ERPLibre on remote servers via SSH
directly from make and the interactive todo.py CLI, since only
local deployment was previously supported.
- New conf/make.ssh.Makefile with 11 targets: ssh_check, ssh_push,
ssh_install, ssh_run, ssh_stop, ssh_restart, ssh_status, ssh_logs,
ssh_make, ssh_install_systemd, ssh_install_nginx
- Variables: SSH_HOST (required), SSH_USER, SSH_PORT, SSH_KEY,
SSH_PATH, SSH_TARGET, SSH_DOMAIN, SSH_ADMIN_EMAIL
- Execute > Deploy menu extended with 11 SSH options in todo.py
- All strings translated fr/en in todo_i18n.py
Generated by Claude Code 2.1.101 model claude-sonnet-4-6
Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-04-10 22:53:07 -04:00
} ,
" SSH - Sync files (rsync) " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 🔄 SSH - Synchroniser les fichiers (rsync) " ,
" en " : " 🔄 SSH - Sync files (rsync) " ,
[ADD] make: add SSH remote deployment targets and todo.py integration
Enable deploying and managing ERPLibre on remote servers via SSH
directly from make and the interactive todo.py CLI, since only
local deployment was previously supported.
- New conf/make.ssh.Makefile with 11 targets: ssh_check, ssh_push,
ssh_install, ssh_run, ssh_stop, ssh_restart, ssh_status, ssh_logs,
ssh_make, ssh_install_systemd, ssh_install_nginx
- Variables: SSH_HOST (required), SSH_USER, SSH_PORT, SSH_KEY,
SSH_PATH, SSH_TARGET, SSH_DOMAIN, SSH_ADMIN_EMAIL
- Execute > Deploy menu extended with 11 SSH options in todo.py
- All strings translated fr/en in todo_i18n.py
Generated by Claude Code 2.1.101 model claude-sonnet-4-6
Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-04-10 22:53:07 -04:00
} ,
" SSH - Install ERPLibre " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 📦 SSH - Installer ERPLibre " ,
" en " : " 📦 SSH - Install ERPLibre " ,
[ADD] make: add SSH remote deployment targets and todo.py integration
Enable deploying and managing ERPLibre on remote servers via SSH
directly from make and the interactive todo.py CLI, since only
local deployment was previously supported.
- New conf/make.ssh.Makefile with 11 targets: ssh_check, ssh_push,
ssh_install, ssh_run, ssh_stop, ssh_restart, ssh_status, ssh_logs,
ssh_make, ssh_install_systemd, ssh_install_nginx
- Variables: SSH_HOST (required), SSH_USER, SSH_PORT, SSH_KEY,
SSH_PATH, SSH_TARGET, SSH_DOMAIN, SSH_ADMIN_EMAIL
- Execute > Deploy menu extended with 11 SSH options in todo.py
- All strings translated fr/en in todo_i18n.py
Generated by Claude Code 2.1.101 model claude-sonnet-4-6
Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-04-10 22:53:07 -04:00
} ,
" SSH - Start Odoo " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 🟢 SSH - Démarrer Odoo " ,
" en " : " 🟢 SSH - Start Odoo " ,
[ADD] make: add SSH remote deployment targets and todo.py integration
Enable deploying and managing ERPLibre on remote servers via SSH
directly from make and the interactive todo.py CLI, since only
local deployment was previously supported.
- New conf/make.ssh.Makefile with 11 targets: ssh_check, ssh_push,
ssh_install, ssh_run, ssh_stop, ssh_restart, ssh_status, ssh_logs,
ssh_make, ssh_install_systemd, ssh_install_nginx
- Variables: SSH_HOST (required), SSH_USER, SSH_PORT, SSH_KEY,
SSH_PATH, SSH_TARGET, SSH_DOMAIN, SSH_ADMIN_EMAIL
- Execute > Deploy menu extended with 11 SSH options in todo.py
- All strings translated fr/en in todo_i18n.py
Generated by Claude Code 2.1.101 model claude-sonnet-4-6
Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-04-10 22:53:07 -04:00
} ,
" SSH - Stop Odoo " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 🔴 SSH - Arrêter Odoo " ,
" en " : " 🔴 SSH - Stop Odoo " ,
[ADD] make: add SSH remote deployment targets and todo.py integration
Enable deploying and managing ERPLibre on remote servers via SSH
directly from make and the interactive todo.py CLI, since only
local deployment was previously supported.
- New conf/make.ssh.Makefile with 11 targets: ssh_check, ssh_push,
ssh_install, ssh_run, ssh_stop, ssh_restart, ssh_status, ssh_logs,
ssh_make, ssh_install_systemd, ssh_install_nginx
- Variables: SSH_HOST (required), SSH_USER, SSH_PORT, SSH_KEY,
SSH_PATH, SSH_TARGET, SSH_DOMAIN, SSH_ADMIN_EMAIL
- Execute > Deploy menu extended with 11 SSH options in todo.py
- All strings translated fr/en in todo_i18n.py
Generated by Claude Code 2.1.101 model claude-sonnet-4-6
Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-04-10 22:53:07 -04:00
} ,
" SSH - Restart Odoo " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 🔁 SSH - Redémarrer Odoo " ,
" en " : " 🔁 SSH - Restart Odoo " ,
[ADD] make: add SSH remote deployment targets and todo.py integration
Enable deploying and managing ERPLibre on remote servers via SSH
directly from make and the interactive todo.py CLI, since only
local deployment was previously supported.
- New conf/make.ssh.Makefile with 11 targets: ssh_check, ssh_push,
ssh_install, ssh_run, ssh_stop, ssh_restart, ssh_status, ssh_logs,
ssh_make, ssh_install_systemd, ssh_install_nginx
- Variables: SSH_HOST (required), SSH_USER, SSH_PORT, SSH_KEY,
SSH_PATH, SSH_TARGET, SSH_DOMAIN, SSH_ADMIN_EMAIL
- Execute > Deploy menu extended with 11 SSH options in todo.py
- All strings translated fr/en in todo_i18n.py
Generated by Claude Code 2.1.101 model claude-sonnet-4-6
Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-04-10 22:53:07 -04:00
} ,
" SSH - Service status " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 📊 SSH - Statut du service " ,
" en " : " 📊 SSH - Service status " ,
[ADD] make: add SSH remote deployment targets and todo.py integration
Enable deploying and managing ERPLibre on remote servers via SSH
directly from make and the interactive todo.py CLI, since only
local deployment was previously supported.
- New conf/make.ssh.Makefile with 11 targets: ssh_check, ssh_push,
ssh_install, ssh_run, ssh_stop, ssh_restart, ssh_status, ssh_logs,
ssh_make, ssh_install_systemd, ssh_install_nginx
- Variables: SSH_HOST (required), SSH_USER, SSH_PORT, SSH_KEY,
SSH_PATH, SSH_TARGET, SSH_DOMAIN, SSH_ADMIN_EMAIL
- Execute > Deploy menu extended with 11 SSH options in todo.py
- All strings translated fr/en in todo_i18n.py
Generated by Claude Code 2.1.101 model claude-sonnet-4-6
Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-04-10 22:53:07 -04:00
} ,
" SSH - View logs " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 📜 SSH - Voir les logs " ,
" en " : " 📜 SSH - View logs " ,
[ADD] make: add SSH remote deployment targets and todo.py integration
Enable deploying and managing ERPLibre on remote servers via SSH
directly from make and the interactive todo.py CLI, since only
local deployment was previously supported.
- New conf/make.ssh.Makefile with 11 targets: ssh_check, ssh_push,
ssh_install, ssh_run, ssh_stop, ssh_restart, ssh_status, ssh_logs,
ssh_make, ssh_install_systemd, ssh_install_nginx
- Variables: SSH_HOST (required), SSH_USER, SSH_PORT, SSH_KEY,
SSH_PATH, SSH_TARGET, SSH_DOMAIN, SSH_ADMIN_EMAIL
- Execute > Deploy menu extended with 11 SSH options in todo.py
- All strings translated fr/en in todo_i18n.py
Generated by Claude Code 2.1.101 model claude-sonnet-4-6
Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-04-10 22:53:07 -04:00
} ,
" SSH - Run make target " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 🧰 SSH - Exécuter une cible make " ,
" en " : " 🧰 SSH - Run make target " ,
[ADD] make: add SSH remote deployment targets and todo.py integration
Enable deploying and managing ERPLibre on remote servers via SSH
directly from make and the interactive todo.py CLI, since only
local deployment was previously supported.
- New conf/make.ssh.Makefile with 11 targets: ssh_check, ssh_push,
ssh_install, ssh_run, ssh_stop, ssh_restart, ssh_status, ssh_logs,
ssh_make, ssh_install_systemd, ssh_install_nginx
- Variables: SSH_HOST (required), SSH_USER, SSH_PORT, SSH_KEY,
SSH_PATH, SSH_TARGET, SSH_DOMAIN, SSH_ADMIN_EMAIL
- Execute > Deploy menu extended with 11 SSH options in todo.py
- All strings translated fr/en in todo_i18n.py
Generated by Claude Code 2.1.101 model claude-sonnet-4-6
Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-04-10 22:53:07 -04:00
} ,
" SSH - Install systemd service " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 🧩 SSH - Installer le service systemd " ,
" en " : " 🧩 SSH - Install systemd service " ,
[ADD] make: add SSH remote deployment targets and todo.py integration
Enable deploying and managing ERPLibre on remote servers via SSH
directly from make and the interactive todo.py CLI, since only
local deployment was previously supported.
- New conf/make.ssh.Makefile with 11 targets: ssh_check, ssh_push,
ssh_install, ssh_run, ssh_stop, ssh_restart, ssh_status, ssh_logs,
ssh_make, ssh_install_systemd, ssh_install_nginx
- Variables: SSH_HOST (required), SSH_USER, SSH_PORT, SSH_KEY,
SSH_PATH, SSH_TARGET, SSH_DOMAIN, SSH_ADMIN_EMAIL
- Execute > Deploy menu extended with 11 SSH options in todo.py
- All strings translated fr/en in todo_i18n.py
Generated by Claude Code 2.1.101 model claude-sonnet-4-6
Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-04-10 22:53:07 -04:00
} ,
" SSH - Configure nginx + SSL " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 🔒 SSH - Configurer nginx + SSL " ,
" en " : " 🔒 SSH - Configure nginx + SSL " ,
[ADD] make: add SSH remote deployment targets and todo.py integration
Enable deploying and managing ERPLibre on remote servers via SSH
directly from make and the interactive todo.py CLI, since only
local deployment was previously supported.
- New conf/make.ssh.Makefile with 11 targets: ssh_check, ssh_push,
ssh_install, ssh_run, ssh_stop, ssh_restart, ssh_status, ssh_logs,
ssh_make, ssh_install_systemd, ssh_install_nginx
- Variables: SSH_HOST (required), SSH_USER, SSH_PORT, SSH_KEY,
SSH_PATH, SSH_TARGET, SSH_DOMAIN, SSH_ADMIN_EMAIL
- Execute > Deploy menu extended with 11 SSH options in todo.py
- All strings translated fr/en in todo_i18n.py
Generated by Claude Code 2.1.101 model claude-sonnet-4-6
Co-Authored-By: Mathieu Benoit <mathben@technolibre.ca>
2026-04-10 22:53:07 -04:00
} ,
" Remote host (user@hostname or hostname): " : {
" fr " : " Hôte distant (user@hostname ou hostname) : " ,
" en " : " Remote host (user@hostname or hostname): " ,
} ,
" SSH user (default: erplibre): " : {
" fr " : " Utilisateur SSH (défaut: erplibre) : " ,
" en " : " SSH user (default: erplibre): " ,
} ,
" SSH port (default: 22): " : {
" fr " : " Port SSH (défaut: 22) : " ,
" en " : " SSH port (default: 22): " ,
} ,
" SSH key path (default: ~/.ssh/id_rsa, empty for none): " : {
" fr " : " Chemin de la clé SSH (défaut: ~/.ssh/id_rsa, vide pour aucune) : " ,
" en " : " SSH key path (default: ~/.ssh/id_rsa, empty for none): " ,
} ,
" Remote path (default: ~/erplibre_deploy_2): " : {
" fr " : " Chemin distant (défaut: ~/erplibre_deploy_2) : " ,
" en " : " Remote path (default: ~/erplibre_deploy_2): " ,
} ,
" Make target to run remotely: " : {
" fr " : " Cible make à exécuter à distance : " ,
" en " : " Make target to run remotely: " ,
} ,
" Domain name (e.g.: example.com): " : {
" fr " : " Nom de domaine (ex: example.com) : " ,
" en " : " Domain name (e.g.: example.com): " ,
} ,
" Admin email for SSL certificate: " : {
" fr " : " Email administrateur pour le certificat SSL : " ,
" en " : " Admin email for SSL certificate: " ,
} ,
" SSH host is required! " : {
" fr " : " L ' hôte SSH est requis! " ,
" en " : " SSH host is required! " ,
} ,
2026-03-07 02:59:07 -05:00
# Language selection
2026-03-12 05:31:45 -04:00
" Choose language / Choisir la langue " : {
2026-03-07 02:59:07 -05:00
" fr " : " Choisir la langue / Choose language " ,
" en " : " Choose language / Choisir la langue " ,
} ,
2026-03-12 05:31:45 -04:00
" French " : {
2026-03-07 02:59:07 -05:00
" fr " : " Francais " ,
" en " : " French " ,
} ,
2026-03-12 05:31:45 -04:00
" English " : {
2026-03-07 02:59:07 -05:00
" fr " : " Anglais " ,
" en " : " English " ,
} ,
2026-03-12 05:31:45 -04:00
" Language changed to: English " : {
2026-03-07 02:59:07 -05:00
" fr " : " Langue changée pour : Francais " ,
" en " : " Language changed to: English " ,
} ,
2026-03-12 05:31:45 -04:00
" TODO execution time " : {
2026-03-07 02:59:07 -05:00
" fr " : " Temps d ' exécution TODO " ,
" en " : " TODO execution time " ,
} ,
2026-03-12 05:31:45 -04:00
" Keyboard interrupt " : {
2026-03-07 02:59:07 -05:00
" fr " : " Interruption clavier " ,
" en " : " Keyboard interrupt " ,
} ,
2026-08-07 03:22:26 -04:00
# GPT code section
" GPT code - AI assistant tools " : {
" fr " : " 🤖 GPT code - Outils d ' assistant IA " ,
" en " : " 🤖 GPT code - AI assistant tools " ,
} ,
2026-03-12 05:31:45 -04:00
" AI assistant tools for development! " : {
2026-03-10 02:05:53 -04:00
" fr " : " Outils d ' assistant IA pour le développement! " ,
" en " : " AI assistant tools for development! " ,
} ,
2026-03-12 05:31:45 -04:00
" Configure Claude Code configurations " : {
2026-03-12 04:37:19 -04:00
" fr " : " Configurer les configurations Claude Code " ,
" en " : " Configure Claude Code configurations " ,
} ,
2026-03-12 05:31:45 -04:00
" Commit - OCA/Odoo commit command " : {
2026-03-12 04:37:19 -04:00
" fr " : " Commit - Commande de commit OCA/Odoo " ,
" en " : " Commit - OCA/Odoo commit command " ,
} ,
2026-03-12 05:31:45 -04:00
" Todo Add Command - Add a command to todo.py menu " : {
2026-03-12 04:37:19 -04:00
" fr " : " Todo Add Command - Ajouter une commande au menu todo.py " ,
" en " : " Todo Add Command - Add a command to todo.py menu " ,
2026-03-10 02:05:53 -04:00
} ,
2026-03-12 05:31:45 -04:00
" Enter your full name: " : {
2026-03-10 02:05:53 -04:00
" fr " : " Entrez votre nom complet : " ,
" en " : " Enter your full name: " ,
} ,
2026-03-12 05:31:45 -04:00
" Enter your email: " : {
2026-03-10 02:05:53 -04:00
" fr " : " Entrez votre courriel : " ,
" en " : " Enter your email: " ,
} ,
2026-03-12 05:31:45 -04:00
" Deploy Claude Code commands! " : {
2026-03-12 04:37:19 -04:00
" fr " : " Déployer les commandes Claude Code! " ,
" en " : " Deploy Claude Code commands! " ,
} ,
2026-03-12 05:31:45 -04:00
" Show installed custom commands " : {
2026-03-12 04:37:19 -04:00
" fr " : " Afficher les commandes personnalisées installées " ,
" en " : " Show installed custom commands " ,
} ,
2026-03-12 05:31:45 -04:00
" No custom commands found in ~/.claude/commands/ " : {
2026-03-12 04:37:19 -04:00
" fr " : " Aucune commande personnalisée trouvée dans ~/.claude/commands/ " ,
" en " : " No custom commands found in ~/.claude/commands/ " ,
} ,
2026-03-12 05:31:45 -04:00
" Claude Code custom commands: " : {
2026-03-12 04:37:19 -04:00
" fr " : " Commandes personnalisées Claude Code : " ,
" en " : " Claude Code custom commands: " ,
} ,
2026-03-12 05:31:45 -04:00
" File already exists: " : {
2026-03-12 04:37:19 -04:00
" fr " : " Le fichier existe déjà : " ,
" en " : " File already exists: " ,
} ,
2026-03-12 05:31:45 -04:00
" Do you want to overwrite the file? (y/Y): " : {
2026-03-12 04:37:19 -04:00
" fr " : " Voulez-vous écraser le fichier? (y/Y) : " ,
" en " : " Do you want to overwrite the file? (y/Y): " ,
} ,
2026-03-12 05:31:45 -04:00
" Nothing to do. " : {
2026-03-12 04:37:19 -04:00
" fr " : " Rien à faire. " ,
" en " : " Nothing to do. " ,
} ,
2026-03-12 05:31:45 -04:00
" File created successfully: " : {
2026-03-12 04:37:19 -04:00
" fr " : " Fichier créé avec succès : " ,
" en " : " File created successfully: " ,
} ,
2026-03-12 05:31:45 -04:00
" Error creating file: " : {
2026-03-10 02:05:53 -04:00
" fr " : " Erreur lors de la création du fichier : " ,
" en " : " Error creating file: " ,
} ,
2026-04-11 00:13:56 -04:00
# NTFY section
" Deploy - Install NTFY notification server " : {
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" fr " : " 🔔 Déployer - Installer le serveur de notifications NTFY " ,
" en " : " 🔔 Deploy - Install NTFY notification server " ,
2026-04-11 00:13:56 -04:00
} ,
" Deploy a local NTFY push notification server (Ubuntu/Arch) " : {
" fr " : " Déployer un serveur local de notifications NTFY (Ubuntu/Arch) " ,
" en " : " Deploy a local NTFY push notification server (Ubuntu/Arch) " ,
} ,
" NTFY server port (default: 8080): " : {
" fr " : " Port du serveur NTFY (défaut : 8080) : " ,
" en " : " NTFY server port (default: 8080): " ,
} ,
" NTFY base URL " : {
" fr " : " URL de base NTFY " ,
" en " : " NTFY base URL " ,
} ,
" Installing NTFY server (requires sudo)... " : {
" fr " : " Installation du serveur NTFY (sudo requis)... " ,
" en " : " Installing NTFY server (requires sudo)... " ,
} ,
" NTFY server installed and started successfully! " : {
" fr " : " Serveur NTFY installé et démarré avec succès! " ,
" en " : " NTFY server installed and started successfully! " ,
} ,
" Error installing NTFY server: " : {
" fr " : " Erreur lors de l ' installation du serveur NTFY : " ,
" en " : " Error installing NTFY server: " ,
} ,
" NTFY install script not found: " : {
" fr " : " Script d ' installation NTFY introuvable : " ,
" en " : " NTFY install script not found: " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
# QEMU / KVM (libvirt) VM deployment
" QEMU/KVM - Deploy an Ubuntu VM (libvirt) " : {
" fr " : " 💻 QEMU/KVM - Déployer une VM Ubuntu (libvirt) " ,
" en " : " 💻 QEMU/KVM - Deploy an Ubuntu VM (libvirt) " ,
} ,
" Deploy a QEMU/KVM virtual machine (libvirt)! " : {
" fr " : " Déployer une machine virtuelle QEMU/KVM (libvirt) ! " ,
" en " : " Deploy a QEMU/KVM virtual machine (libvirt)! " ,
} ,
" QEMU deploy script not found: " : {
" fr " : " Script de déploiement QEMU introuvable : " ,
" en " : " QEMU deploy script not found: " ,
} ,
" Deploy a new VM " : {
" fr " : " Déployer une nouvelle VM " ,
" en " : " Deploy a new VM " ,
} ,
" Preview a deployment (dry-run, no sudo) " : {
" fr " : " 🔍 Prévisualiser un déploiement (dry-run, sans sudo) " ,
" en " : " 🔍 Preview a deployment (dry-run, no sudo) " ,
} ,
" Download a cloud image only " : {
" fr " : " ⬇ Télécharger seulement une image cloud " ,
" en " : " ⬇ Download a cloud image only " ,
} ,
" List VMs (virsh list --all) " : {
" fr " : " 📋 Lister les VM (virsh list --all) " ,
" en " : " 📋 List VMs (virsh list --all) " ,
} ,
" Show a VM IP address " : {
" fr " : " 🌐 Afficher l ' adresse IP d ' une VM " ,
" en " : " 🌐 Show a VM IP address " ,
} ,
" List available images and specs " : {
" fr " : " 🗂 Lister les images disponibles et leurs specs " ,
" en " : " 🗂 List available images and specs " ,
} ,
" Distribution: " : {
" fr " : " Distribution : " ,
" en " : " Distribution: " ,
} ,
" Choice (number or name, default: ubuntu): " : {
" fr " : " Choix (numéro ou nom, défaut : ubuntu) : " ,
" en " : " Choice (number or name, default: ubuntu): " ,
} ,
" Invalid selection, using ubuntu " : {
" fr " : " Sélection invalide, utilisation d ' ubuntu " ,
" en " : " Invalid selection, using ubuntu " ,
} ,
" Version for " : {
" fr " : " Version des " ,
" en " : " Version for " ,
} ,
" Versions for " : {
" fr " : " Versions des " ,
" en " : " Versions of " ,
} ,
" Architecture: " : {
" fr " : " Architecture : " ,
" en " : " Architecture: " ,
} ,
" native " : {
" fr " : " native " ,
" en " : " native " ,
} ,
" IBM Z — emulated, slow on x86; Ubuntu only " : {
" fr " : " IBM Z — émulé, lent sur x86 ; Ubuntu uniquement " ,
" en " : " IBM Z — emulated, slow on x86; Ubuntu only " ,
} ,
" IBM Z — emulated, slow; Ubuntu only " : {
" fr " : " IBM Z — émulé, lent ; Ubuntu uniquement " ,
" en " : " IBM Z — emulated, slow; Ubuntu only " ,
} ,
" ARM 64-bit — emulated, slow " : {
" fr " : " ARM 64 bits — émulé, lent " ,
" en " : " ARM 64-bit — emulated, slow " ,
} ,
" emulated, slow " : {
" fr " : " émulé, lent " ,
" en " : " emulated, slow " ,
} ,
" This architecture is emulated (TCG): boot and install are "
" much slower than the native one. " : {
" fr " : " Cette architecture est émulée (TCG) : le boot et l ' installation "
" sont bien plus lents que l ' architecture native. " ,
" en " : " This architecture is emulated (TCG): boot and install are "
" much slower than the native one. " ,
} ,
" images for this arch only exist for: " : {
" fr " : " images pour cette architecture disponibles seulement pour : " ,
" en " : " images for this arch only exist for: " ,
} ,
" All supported architectures " : {
" fr " : " Toutes les architectures supportées " ,
" en " : " All supported architectures " ,
} ,
" installation " : {
" fr " : " installation " ,
" en " : " installation " ,
} ,
" Resources per VM (x1 = catalog minimum): " : {
" fr " : " Ressources par VM (x1 = minimum catalogue) : " ,
" en " : " Resources per VM (x1 = catalog minimum): " ,
} ,
" Host: " : {
" fr " : " Hôte : " ,
" en " : " Host: " ,
} ,
" free " : {
" fr " : " libres " ,
" en " : " free " ,
} ,
" free RAM unknown " : {
" fr " : " RAM libre inconnue " ,
" en " : " free RAM unknown " ,
} ,
" > host free RAM " : {
" fr " : " > RAM libre de l ' hôte " ,
" en " : " > host free RAM " ,
} ,
[ADD] todo : dire la place qui reste sous le plan de déploiement
La ligne de totaux annonçait « ~126 G » sans dire sur quoi : la demande
seule ne dit pas si ça rentre, et on l'apprenait au déploiement. Elle dit
maintenant la demande, ce qui reste et la capacité — « ~126 G / 20 G libres
sur 270 G » — et prévient dès que le plan dépasse. Les trois limites (RAM,
disque, cœurs) s'affichent ensemble : n'en montrer qu'une cachait les
autres. Sur Proxmox, la place vient du stockage choisi, que « pvesm status »
donnait déjà.
Deux défauts trouvés en le faisant : la marque de génération, exigée de tous
les widgets, faisait taire chaque réglage commun de l'écran Proxmox, et
« [x1] » disparaissait, lu comme une balise Rich.
--- EN ---
The totals line said "~126 G" without saying out of what: the demand alone
does not tell whether it fits, and one found out at deploy time. It now says
the demand, what is left and the capacity — "~126 G / 20 G free of 270 G" —
and warns as soon as the plan exceeds it. The three limits (RAM, disk,
cores) show together: showing only one hid the others. On Proxmox the room
comes from the chosen storage, which "pvesm status" already gave.
Two defects found on the way: the generation mark, required of every widget,
silenced each common setting on the Proxmox screen, and "[x1]" vanished,
read as a Rich tag.
Assisted-by: Claude Opus 5
2026-08-24 01:11:09 -04:00
" > host free disk " : {
" fr " : " > disque libre de l ' hôte " ,
" en " : " > host free disk " ,
} ,
" free of " : {
" fr " : " libres sur " ,
" en " : " free of " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" total RAM " : {
" fr " : " RAM totale " ,
" en " : " total RAM " ,
} ,
" Choice (1-4, default 1): " : {
" fr " : " Choix (1-4, défaut 1) : " ,
" en " : " Choice (1-4, default 1): " ,
} ,
" Choice (1-5, default 1): " : {
" fr " : " Choix (1-5, défaut 1) : " ,
" en " : " Choice (1-5, default 1): " ,
} ,
" Custom - set vCPU, RAM and disk " : {
" fr " : " Personnalisé — choisir vCPU, RAM et disque " ,
" en " : " Custom — set vCPU, RAM and disk " ,
} ,
" custom " : {
" fr " : " personnalisé " ,
" en " : " custom " ,
} ,
" varies " : {
" fr " : " varié " ,
" en " : " varies " ,
} ,
" vCPU per VM, blank = keep " : {
" fr " : " vCPU par VM, vide = garder " ,
" en " : " vCPU per VM, blank = keep " ,
} ,
" New vCPU count, blank = keep " : {
" fr " : " Nouveau nombre de vCPU, vide = garder " ,
" en " : " New vCPU count, blank = keep " ,
} ,
" Invalid vCPU count. " : {
" fr " : " Nombre de vCPU invalide. " ,
" en " : " Invalid vCPU count. " ,
} ,
" More vCPU than host cores " : {
" fr " : " Plus de vCPU que de cœurs sur l ' hôte " ,
" en " : " More vCPU than host cores " ,
} ,
" > host cores " : {
" fr " : " > cœurs de l ' hôte " ,
" en " : " > host cores " ,
} ,
" Total vCPU (all running): " : {
" fr " : " vCPU total (toutes démarrées) : " ,
" en " : " Total vCPU (all running): " ,
} ,
" Deploy VM(s) (one or many) " : {
" fr " : " 🚀 Déployer une ou plusieurs VM " ,
" en " : " 🚀 Deploy VM(s) (one or many) " ,
} ,
" Deploy ERPLibre VM(s)! " : {
" fr " : " Déployer une ou plusieurs VM ERPLibre ! " ,
" en " : " Deploy ERPLibre VM(s)! " ,
} ,
" VM names (default = auto): " : {
" fr " : " Noms des VM (défaut = auto) : " ,
" en " : " VM names (default = auto): " ,
} ,
" Rename which VMs? (numbers, comma-separated; blank = none): " : {
" fr " : " Renommer quelles VM ? (numéros séparés par des virgules ; "
" vide = aucune) : " ,
" en " : " Rename which VMs? (numbers, comma-separated; blank = none): " ,
} ,
" -> " : {
" fr " : " -> " ,
" en " : " -> " ,
} ,
" VMs (default = no change): " : {
" fr " : " VM (défaut = aucune modification) : " ,
" en " : " VMs (default = no change): " ,
} ,
" Modify which VMs? (numbers, comma-separated; blank = none): " : {
" fr " : " Modifier quelles VM ? (numéros séparés par des virgules ; "
" vide = aucune) : " ,
" en " : " Modify which VMs? (numbers, comma-separated; blank = none): " ,
} ,
" new name (blank = keep): " : {
" fr " : " nouveau nom (vide = garder) : " ,
" en " : " new name (blank = keep): " ,
} ,
" New disk size in G, blank = keep " : {
" fr " : " Nouvelle taille disque en G, vide = garder " ,
" en " : " New disk size in G, blank = keep " ,
} ,
" New RAM in MB, blank = keep " : {
" fr " : " Nouvelle RAM en Mo, vide = garder " ,
" en " : " New RAM in MB, blank = keep " ,
} ,
" Duplicate names detected; keeping as entered. " : {
" fr " : " Noms en double détectés ; conservés tels quels. " ,
" en " : " Duplicate names detected; keeping as entered. " ,
} ,
" Preview (dry-run): " : {
" fr " : " Aperçu (dry-run) : " ,
" en " : " Preview (dry-run): " ,
} ,
" (includes emulated architectures — some VMs are slow) " : {
" fr " : " (inclut des architectures émulées — certaines VM sont lentes) " ,
" en " : " (includes emulated architectures — some VMs are slow) " ,
} ,
" install monitoring " : {
" fr " : " suivi d ' installation " ,
" en " : " install monitoring " ,
} ,
" Navigation telemetry (TUI) " : {
" fr " : " 📊 Télémétrie de navigation (TUI) " ,
" en " : " 📊 Navigation telemetry (TUI) " ,
} ,
2026-08-07 03:22:26 -04:00
" Configuration " : {
" fr " : " ⚙ Configuration " ,
" en " : " ⚙ Configuration " ,
} ,
" Interface " : {
" fr " : " Interface " ,
" en " : " Interface " ,
} ,
" Maintenance " : {
" fr " : " Maintenance " ,
" en " : " Maintenance " ,
} ,
" Language / Langue " : {
" fr " : " 🌐 Langue / Language " ,
" en " : " 🌐 Language / Langue " ,
} ,
" QEMU deployment interface " : {
" fr " : " 🖥 Interface de déploiement QEMU " ,
" en " : " 🖥 QEMU deployment interface " ,
} ,
" Display while deploying " : {
" fr " : " 📜 Affichage pendant le déploiement " ,
" en " : " 📜 Display while deploying " ,
} ,
" Ask every time " : {
" fr " : " Demander à chaque fois " ,
" en " : " Ask every time " ,
} ,
" TUI form " : {
" fr " : " Formulaire TUI " ,
" en " : " TUI form " ,
} ,
" Classic questions (line by line) " : {
" fr " : " Questions classiques (ligne par ligne) " ,
" en " : " Classic questions (line by line) " ,
} ,
" CLI output (easy to copy) " : {
" fr " : " Sortie CLI (facile à copier) " ,
" en " : " CLI output (easy to copy) " ,
} ,
" TUI, collapsible blocks per VM " : {
" fr " : " TUI, blocs repliables par VM " ,
" en " : " TUI, collapsible blocks per VM " ,
} ,
" Choice (number, blank = keep): " : {
" fr " : " Choix (numéro, vide = garder) : " ,
" en " : " Choice (number, blank = keep): " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" SSH port forwarding (open Odoo in the browser) " : {
" fr " : " 🔌 Redirection de port SSH (ouvrir Odoo dans le navigateur) " ,
" en " : " 🔌 SSH port forwarding (open Odoo in the browser) " ,
} ,
" SSH port forwarding " : {
" fr " : " Redirection de port SSH " ,
" en " : " SSH port forwarding " ,
} ,
" Host (number or name): " : {
" fr " : " Hôte (numéro ou nom) : " ,
" en " : " Host (number or name): " ,
} ,
" Remote port (default: " : {
" fr " : " Port distant (défaut : " ,
" en " : " Remote port (default: " ,
} ,
" Local port (default: " : {
" fr " : " Port local (défaut : " ,
" en " : " Local port (default: " ,
} ,
" virsh is missing: libvirt is not installed here. " : {
" fr " : " virsh est absent : libvirt n ' est pas installé ici. " ,
" en " : " virsh is missing: libvirt is not installed here. " ,
} ,
" Every VM command will fail until it is. " : {
" fr " : " Toutes les commandes VM échoueront tant que ce sera le cas. " ,
" en " : " Every VM command will fail until it is. " ,
} ,
" Install the QEMU/libvirt tools now? (Y/n): " : {
" fr " : " Installer les outils QEMU/libvirt maintenant ? "
" (O/n, défaut : oui) : " ,
" en " : " Install the QEMU/libvirt tools now? (Y/n, default: yes): " ,
} ,
" libvirt is available. " : {
" fr " : " libvirt est disponible. " ,
" en " : " libvirt is available. " ,
} ,
" virsh still missing; a reboot may be required. " : {
" fr " : " virsh toujours absent ; un redémarrage est peut-être requis. " ,
" en " : " virsh still missing; a reboot may be required. " ,
} ,
" No SSH public key found in ~/.ssh. " : {
" fr " : " Aucune clé publique SSH trouvée dans ~/.ssh. " ,
" en " : " No SSH public key found in ~/.ssh. " ,
} ,
" Without one the VMs start with no SSH access. " : {
" fr " : " Sans elle, les VM démarrent sans accès SSH. " ,
" en " : " Without one the VMs start with no SSH access. " ,
} ,
" Generate one now? (Y/n): " : {
" fr " : " En générer une maintenant ? (O/n, défaut : oui) : " ,
" en " : " Generate one now? (Y/n, default: yes): " ,
} ,
" Checking the remote port... " : {
" fr " : " Vérification du port distant... " ,
" en " : " Checking the remote port... " ,
} ,
" Nothing is listening on port " : {
" fr " : " Rien n ' écoute sur le port " ,
" en " : " Nothing is listening on port " ,
} ,
" of " : {
" fr " : " de " ,
" en " : " of " ,
} ,
" Start the service there, or continue anyway. " : {
" fr " : " Démarrer le service là-bas, ou continuer quand même. " ,
" en " : " Start the service there, or continue anyway. " ,
} ,
" Continue anyway? (y/N): " : {
" fr " : " Continuer quand même ? (o/N, défaut : non) : " ,
" en " : " Continue anyway? (y/N, default: no): " ,
} ,
" Could not probe the remote port; going on. " : {
" fr " : " Impossible de sonder le port distant ; on continue. " ,
" en " : " Could not probe the remote port; going on. " ,
} ,
" Local port already in use: " : {
" fr " : " Port local déjà occupé : " ,
" en " : " Local port already in use: " ,
} ,
" Try anyway? (y/N): " : {
" fr " : " Essayer quand même ? (o/N, défaut : non) : " ,
" en " : " Try anyway? (y/N, default: no): " ,
} ,
" Local port differs from the remote one. " : {
" fr " : " Le port local diffère du port distant. " ,
" en " : " Local port differs from the remote one. " ,
} ,
" Odoo redirects using web.base.url; check it " : {
" fr " : " Odoo redirige d ' après web.base.url ; vérifier qu ' il " ,
" en " : " Odoo redirects using web.base.url; check it " ,
} ,
" matches http://localhost: " : {
" fr " : " vaut bien http://localhost: " ,
" en " : " matches http://localhost: " ,
} ,
" Ctrl+C closes the tunnel. " : {
" fr " : " Ctrl+C referme le tunnel. " ,
" en " : " Ctrl+C closes the tunnel. " ,
} ,
" Tunnel closed. " : {
" fr " : " Tunnel refermé. " ,
" en " : " Tunnel closed. " ,
} ,
2026-08-07 03:22:26 -04:00
" SSH configuration (~/.ssh/config, ProxyJump) " : {
" fr " : " 🔑 Configuration SSH (~/.ssh/config, ProxyJump) " ,
" en " : " 🔑 SSH configuration (~/.ssh/config, ProxyJump) " ,
} ,
" SSH configuration for QEMU VMs " : {
" fr " : " Configuration SSH des VM QEMU " ,
" en " : " SSH configuration for QEMU VMs " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" Where should the machines come from? " : {
" fr " : " D ' où viennent les machines à configurer ? " ,
" en " : " Where should the machines come from? " ,
} ,
" Local QEMU VMs (virsh) " : {
" fr " : " VM QEMU locales (virsh) " ,
" en " : " Local QEMU VMs (virsh) " ,
} ,
" Hosts from ~/.ssh/config " : {
" fr " : " Hôtes de ~/.ssh/config " ,
" en " : " Hosts from ~/.ssh/config " ,
} ,
" Type a host or an IP " : {
" fr " : " Saisir un hôte ou une IP " ,
" en " : " Type a host or an IP " ,
} ,
" ~/.ssh/config holds no host. " : {
" fr " : " ~/.ssh/config ne contient aucun hôte. " ,
" en " : " ~/.ssh/config holds no host. " ,
} ,
" Which hosts? (numbers, comma-separated; blank = all): " : {
" fr " : " Quels hôtes ? (numéros séparés par des virgules ; "
" vide = tous) : " ,
" en " : " Which hosts? (numbers, comma-separated; blank = all): " ,
} ,
" Host or IP: " : {
" fr " : " Hôte ou IP : " ,
" en " : " Host or IP: " ,
} ,
" User " : {
" fr " : " Utilisateur " ,
" en " : " User " ,
} ,
" Name for ~/.ssh/config " : {
" fr " : " Nom pour ~/.ssh/config " ,
" en " : " Name for ~/.ssh/config " ,
} ,
" Depth (1 = these machines only, default: " : {
" fr " : " Profondeur (1 = ces machines seulement, défaut : " ,
" en " : " Depth (1 = these machines only, default: " ,
} ,
2026-08-07 03:22:26 -04:00
" Which VMs? (numbers, comma-separated; blank = all): " : {
" fr " : " Quelles VM ? (numéros séparés par des virgules ; "
" vide = toutes) : " ,
" en " : " Which VMs? (numbers, comma-separated; blank = all): " ,
} ,
" Depth (default: " : {
" fr " : " Profondeur (défaut : " ,
" en " : " Depth (default: " ,
} ,
" entry " : {
" fr " : " entrée " ,
" en " : " entry " ,
} ,
" written " : {
" fr " : " écrite(s) " ,
" en " : " written " ,
} ,
" skipped " : {
" fr " : " ignorée(s) " ,
" en " : " skipped " ,
} ,
" Level " : {
" fr " : " Niveau " ,
" en " : " Level " ,
} ,
" machines to probe " : {
" fr " : " machines à sonder " ,
" en " : " machines to probe " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" SSH refused the identity. " : {
" fr " : " SSH a refusé l ' identité. " ,
" en " : " SSH refused the identity. " ,
} ,
" Existing key: " : {
" fr " : " Clé existante : " ,
" en " : " Existing key: " ,
} ,
" Deploy it on this host (ssh-copy-id)? (Y/n): " : {
" fr " : " La déployer sur cet hôte (ssh-copy-id) ? "
" (O/n, défaut : oui) : " ,
" en " : " Deploy it on this host (ssh-copy-id)? (Y/n, default: yes): " ,
} ,
" No SSH key in ~/.ssh. " : {
" fr " : " Aucune clé SSH dans ~/.ssh. " ,
" en " : " No SSH key in ~/.ssh. " ,
} ,
" Create one and deploy it? (Y/n): " : {
" fr " : " En créer une et la déployer ? (O/n, défaut : oui) : " ,
" en " : " Create one and deploy it? (Y/n, default: yes): " ,
} ,
" access refused " : {
" fr " : " accès refusé " ,
" en " : " access refused " ,
} ,
" timed out " : {
" fr " : " délai dépassé " ,
" en " : " timed out " ,
} ,
" unreachable " : {
" fr " : " injoignable " ,
" en " : " unreachable " ,
} ,
" no QEMU/libvirt here " : {
" fr " : " pas de QEMU/libvirt ici " ,
" en " : " no QEMU/libvirt here " ,
} ,
[IMP] qemu: a VM one can reach, and a first boot that does not stall
The monitor froze the address given at launch, so it lost the VM as soon as
cloud-init renamed the host and DHCP handed out another lease. It now
re-resolves at each attempt, in the views too, and reads virsh without sudo
— « sudo -n » fails in a detached session with no tty.
First boot also stopped paying for what it does not need: the guest agent
leaves cloud-init, snapd and locale-gen go, apt takes the fastest mirror.
The timezone follows the host. And when KVM is missing, the deployment says
so before the wait instead of being mysteriously fifteen times slower.
--- FR ---
Le suivi figeait l'adresse connue au lancement : il perdait donc la VM dès
que cloud-init posait le vrai nom d'hôte et que DHCP donnait un autre bail.
Il la ré-résout désormais à chaque tentative, dans les vues aussi, et lit
virsh sans sudo — « sudo -n » échoue dans une session détachée, sans tty.
Le premier démarrage cesse aussi de payer l'inutile : l'agent invité sort
de cloud-init, snapd et locale-gen disparaissent, apt prend le miroir le
plus rapide. Le fuseau suit l'hôte. Et faute de KVM, le déploiement le dit
avant l'attente, au lieu d'être quinze fois plus lent sans raison visible.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
" virsh present but not accessible " : {
" fr " : " virsh présent mais inaccessible (droits) " ,
" en " : " virsh present but not accessible (permissions) " ,
} ,
" Add the user to the libvirt group there: " : {
" fr " : " Ajoutez l ' utilisateur au groupe libvirt là-bas : " ,
" en " : " Add the user to the libvirt group there: " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" QEMU present, no VM " : {
" fr " : " QEMU présent, aucune VM " ,
" en " : " QEMU present, no VM " ,
} ,
" Create the SSH key if missing and deploy it? (Y/n): " : {
" fr " : " Créer la clé SSH si absente et la déployer ? "
" (O/n, défaut : oui) : " ,
" en " : " Create the SSH key if missing and deploy it? "
" (Y/n, default: yes): " ,
} ,
" Generating an ed25519 SSH key " : {
" fr " : " Génération d ' une clé SSH ed25519 " ,
" en " : " Generating an ed25519 SSH key " ,
} ,
" Cannot generate the key " : {
" fr " : " Impossible de générer la clé " ,
" en " : " Cannot generate the key " ,
} ,
" Deploying the key on " : {
" fr " : " Déploiement de la clé sur " ,
" en " : " Deploying the key on " ,
} ,
" host " : {
" fr " : " hôte " ,
" en " : " host " ,
} ,
" key already accepted " : {
" fr " : " clé déjà acceptée " ,
" en " : " key already accepted " ,
} ,
" deployed " : {
" fr " : " déployée(s) " ,
" en " : " deployed " ,
} ,
" already there " : {
" fr " : " déjà en place " ,
" en " : " already there " ,
} ,
" virt-manager detected " : {
" fr " : " virt-manager détecté " ,
" en " : " virt-manager detected " ,
} ,
" virt-manager: every connection already there " : {
" fr " : " virt-manager : toutes les connexions sont déjà là " ,
" en " : " virt-manager: every connection already there " ,
} ,
" Add the missing connections to virt-manager? (Y/n): " : {
" fr " : " Ajouter les connexions manquantes à virt-manager ? "
" (O/n, défaut : oui) : " ,
" en " : " Add the missing connections to virt-manager? "
" (Y/n, default: yes): " ,
} ,
" Restart virt-manager to see the new connections " : {
" fr " : " Redémarrer virt-manager pour voir les nouvelles connexions " ,
" en " : " Restart virt-manager to see the new connections " ,
} ,
" the names apply live " : {
" fr " : " les noms s ' appliquent à chaud " ,
" en " : " the names apply live " ,
} ,
2026-08-07 03:22:26 -04:00
" SSH hosts written " : {
" fr " : " Hôtes SSH écrits " ,
" en " : " SSH hosts written " ,
} ,
" via " : {
" fr " : " via " ,
" en " : " via " ,
} ,
[ADD] migration: see and repair the website COW views
A copy-on-write view freezes the module view it came from; the module moves
on and the upgrade dies hours later on a missing anchor. These tools
predict, snapshot, diff, neutralize and reset them. The migration screen
gains the real state of each step, replay from any of them, and statistics.
--- FR ---
Une vue copy-on-write fige la vue de module dont elle vient ; le module
évolue et la mise à niveau meurt des heures plus tard sur un point
d'ancrage absent. Ces outils les prévoient, photographient, comparent,
neutralisent et réinitialisent. L'écran de migration gagne l'état réel de
chaque étape, la reprise depuis n'importe laquelle, et des statistiques.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
" Odoo migration interface " : {
" fr " : " 🚚 Interface de la migration Odoo " ,
" en " : " 🚚 Odoo migration interface " ,
} ,
" modules of the list have no code in the active Odoo: " : {
" fr " : " modules de la liste n ' ont plus de code dans l ' Odoo actif : " ,
" en " : " modules of the list have no code in the active Odoo: " ,
} ,
" Odoo cannot uninstall a module whose code is gone; " : {
" fr " : " Odoo ne peut pas désinstaller un module dont le code a "
" disparu ; " ,
" en " : " Odoo cannot uninstall a module whose code is gone; " ,
} ,
" one of them fails the whole uninstall command. " : {
" fr " : " un seul d ' entre eux fait échouer toute la désinstallation. " ,
" en " : " one of them fails the whole uninstall command. " ,
} ,
" are present and can be uninstalled: " : {
" fr " : " sont présents et peuvent être désinstallés : " ,
" en " : " are present and can be uninstalled: " ,
} ,
" No module of the list is present. " : {
" fr " : " Aucun module de la liste n ' est présent. " ,
" en " : " No module of the list is present. " ,
} ,
" Uninstall the present ones, skip the missing " : {
" fr " : " Désinstaller les présents, ignorer les manquants " ,
" en " : " Uninstall the present ones, skip the missing " ,
} ,
" Try the whole list anyway (it will fail) " : {
" fr " : " Tout tenter quand même (ça échouera) " ,
" en " : " Try the whole list anyway (it will fail) " ,
} ,
" Uninstall nothing, continue " : {
" fr " : " Ne rien désinstaller, continuer " ,
" en " : " Uninstall nothing, continue " ,
} ,
" Nothing uninstalled. " : {
" fr " : " Rien de désinstallé. " ,
" en " : " Nothing uninstalled. " ,
} ,
[FIX] migration: un module fautif n'emporte plus tout le lot
« --uninstall » prend une liste virgulée et Odoo annule la transaction
entière au premier échec : soit tout part, soit rien. Mesuré sur une
chaîne 12 → 18 — crm_phone échoue sur une colonne absente de res_users
et fait tomber les 22 autres avec lui, dont huit modules maison sans
code en 13. Ceux-là sont alors montés d'un palier « installed » sans
rien pour les charger, et ne sont partis que trois paliers plus loin,
par accident.
Le lot part toujours d'abord — un démarrage d'Odoo par nom se paierait
cher pour rien. Mais s'il échoue, on reprend un par un : ce qui peut
partir part, et l'on nomme ce qui résiste.
S'ajoute la liste 12 → 13, qui n'existait pas : sans elle l'étape
« Uninstall module » n'avait rien à faire.
--- EN ---
« --uninstall » takes a comma list and Odoo rolls the whole transaction
back on the first failure: all or nothing. Measured on a 12 → 18 chain —
crm_phone fails on a column missing from res_users and drags the other
22 down with it, including eight in-house modules with no code in 13.
Those rode a step up « installed » with nothing to load them, and only
left three steps later, by accident.
The batch still goes first — one Odoo start per name would cost dearly
for nothing. But if it fails, we retry one by one: what can leave
leaves, and we name what resists.
Plus the 12 → 13 list, which did not exist: without it the « Uninstall
module » step had nothing to do.
Assisted-by: Claude Opus 5
2026-08-24 05:29:38 -04:00
" Retrying alone: " : {
" fr " : " Reprise seul : " ,
" en " : " Retrying alone: " ,
} ,
2026-08-23 01:51:28 -04:00
" The uninstall did not take: " : {
" fr " : " La désinstallation n ' a pas pris : " ,
" en " : " The uninstall did not take: " ,
} ,
" Odoo exits 0 even when it removes nothing. " : {
" fr " : " Odoo sort en 0 même quand il ne retire rien. " ,
" en " : " Odoo exits 0 even when it removes nothing. " ,
} ,
" Could not verify the uninstall. " : {
" fr " : " Impossible de vérifier la désinstallation. " ,
" en " : " Could not verify the uninstall. " ,
} ,
[ADD] migration: see and repair the website COW views
A copy-on-write view freezes the module view it came from; the module moves
on and the upgrade dies hours later on a missing anchor. These tools
predict, snapshot, diff, neutralize and reset them. The migration screen
gains the real state of each step, replay from any of them, and statistics.
--- FR ---
Une vue copy-on-write fige la vue de module dont elle vient ; le module
évolue et la mise à niveau meurt des heures plus tard sur un point
d'ancrage absent. Ces outils les prévoient, photographient, comparent,
neutralisent et réinitialisent. L'écran de migration gagne l'état réel de
chaque étape, la reprise depuis n'importe laquelle, et des statistiques.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
" Check the COW views that drifted " : {
" fr " : " Vérifier les vues COW en retard sur leur vue module " ,
" en " : " Check the COW views that drifted " ,
} ,
" Tool not found " : {
" fr " : " Outil introuvable " ,
" en " : " Tool not found " ,
} ,
" To reset one of them onto its module view: " : {
" fr " : " Pour réinitialiser l ' une d ' elles sur sa vue module : " ,
" en " : " To reset one of them onto its module view: " ,
} ,
" Read the diff first: a copy can hold a customisation. " : {
" fr " : " Lire le diff d ' abord : une copie peut porter une "
" personnalisation. " ,
" en " : " Read the diff first: a copy can hold a customisation. " ,
} ,
" Migration statistics (read-only) " : {
" fr " : " 📊 Statistiques de la migration (lecture seule) " ,
" en " : " 📊 Migration statistics (read-only) " ,
} ,
" Choice (0-3, default 1): " : {
" fr " : " Choix (0-3, défaut 1) : " ,
" en " : " Choice (0-3, default 1): " ,
} ,
" Migration statistics " : {
" fr " : " Statistiques de la migration " ,
" en " : " Migration statistics " ,
} ,
" elapsed " : {
" fr " : " écoulé " ,
" en " : " elapsed " ,
} ,
" Level reached " : {
" fr " : " Niveau atteint " ,
" en " : " Level reached " ,
} ,
" Modules " : {
" fr " : " Modules " ,
" en " : " Modules " ,
} ,
" modules " : {
" fr " : " modules " ,
" en " : " modules " ,
} ,
" At the start " : {
" fr " : " Au départ " ,
" en " : " At the start " ,
} ,
" Removed in total " : {
" fr " : " Supprimés au total " ,
" en " : " Removed in total " ,
} ,
" Reported missing " : {
" fr " : " Signalés manquants " ,
" en " : " Reported missing " ,
} ,
" Duplicated " : {
" fr " : " En double " ,
" en " : " Duplicated " ,
} ,
" Migration fixes " : {
" fr " : " Correctifs de migration " ,
" en " : " Migration fixes " ,
} ,
" COW views " : {
" fr " : " Vues COW " ,
" en " : " COW views " ,
} ,
" views " : {
" fr " : " vues " ,
" en " : " views " ,
} ,
" no snapshot " : {
" fr " : " aucun instantané " ,
" en " : " no snapshot " ,
} ,
" Journal " : {
" fr " : " Journal " ,
" en " : " Journal " ,
} ,
" commands " : {
" fr " : " commandes " ,
" en " : " commands " ,
} ,
" recorded decisions " : {
" fr " : " décisions annotées " ,
" en " : " recorded decisions " ,
} ,
" Removed modules, with their reason " : {
" fr " : " Modules supprimés, avec leur justification " ,
" en " : " Removed modules, with their reason " ,
} ,
" Removed modules, comma-separated (copy) " : {
" fr " : " Modules supprimés, séparés par des virgules (à copier) " ,
" en " : " Removed modules, comma-separated (copy) " ,
} ,
" COW views: snapshots and differences " : {
" fr " : " Vues COW : instantanés et différences " ,
" en " : " COW views: snapshots and differences " ,
} ,
" Recorded decisions (journal) " : {
" fr " : " Décisions annotées (journal) " ,
" en " : " Recorded decisions (journal) " ,
} ,
" Executed commands (last 30) " : {
" fr " : " Commandes exécutées (30 dernières) " ,
" en " : " Executed commands (last 30) " ,
} ,
" nothing recorded " : {
" fr " : " rien d ' annoté " ,
" en " : " nothing recorded " ,
} ,
" Need two snapshots to diff. " : {
" fr " : " Il faut deux instantanés pour comparer. " ,
" en " : " Need two snapshots to diff. " ,
} ,
" Diff which two? (e.g. 1,2 — blank to skip) " : {
" fr " : " Comparer lesquels ? (ex. 1,2 — vide pour passer) " ,
" en " : " Diff which two? (e.g. 1,2 — blank to skip) " ,
} ,
" No migration in progress to resume. " : {
" fr " : " Aucune migration en cours à reprendre. " ,
" en " : " No migration in progress to resume. " ,
} ,
" Quit without doing anything " : {
" fr " : " Quitter sans rien faire " ,
" en " : " Quit without doing anything " ,
} ,
" Version bumps " : {
" fr " : " Montées de version " ,
" en " : " Version bumps " ,
} ,
" Step " : {
" fr " : " Étape " ,
" en " : " Step " ,
} ,
" Detail " : {
" fr " : " Détail " ,
" en " : " Detail " ,
} ,
" Enter on a step or a version = replay from there " : {
" fr " : " Entrée sur une étape ou une version = rejouer depuis là " ,
" en " : " Enter on a step or a version = replay from there " ,
} ,
" New migration " : {
" fr " : " Nouvelle migration " ,
" en " : " New migration " ,
} ,
" Keep the zip only " : {
" fr " : " Garder seulement le zip " ,
" en " : " Keep the zip only " ,
} ,
2026-08-07 03:22:26 -04:00
" Reset all preferences " : {
" fr " : " 🧹 Réinitialiser toutes les préférences " ,
" en " : " 🧹 Reset all preferences " ,
} ,
" Preferences reset " : {
" fr " : " Préférences réinitialisées " ,
" en " : " Preferences reset " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" Interface: " : {
" fr " : " Interface : " ,
" en " : " Interface: " ,
} ,
" (change the default in TODO > Configuration) " : {
" fr " : " (changer le défaut dans TODO > Configuration) " ,
" en " : " (change the default in TODO > Configuration) " ,
} ,
" Loading (VM list, branches)... " : {
" fr " : " Chargement (liste des VM, branches)... " ,
" en " : " Loading (VM list, branches)... " ,
} ,
" Architecture " : {
" fr " : " Architecture " ,
" en " : " Architecture " ,
} ,
" Resources per VM " : {
" fr " : " Ressources par VM " ,
" en " : " Resources per VM " ,
} ,
" Install ERPLibre " : {
" fr " : " Installer ERPLibre " ,
" en " : " Install ERPLibre " ,
} ,
" Production (/opt, confined) " : {
" fr " : " Production (/opt, confiné) " ,
" en " : " Production (/opt, confined) " ,
} ,
" Monitoring dashboard " : {
" fr " : " Tableau de bord de suivi " ,
" en " : " Monitoring dashboard " ,
} ,
" Add each VM to ~/.ssh/config " : {
" fr " : " Ajouter chaque VM à ~/.ssh/config " ,
" en " : " Add each VM to ~/.ssh/config " ,
} ,
" Parallelism " : {
" fr " : " Parallélisme " ,
" en " : " Parallelism " ,
} ,
" Name " : {
" fr " : " Nom " ,
" en " : " Name " ,
} ,
" Distro " : {
" fr " : " Distro " ,
" en " : " Distro " ,
} ,
" Version " : {
" fr " : " Version " ,
" en " : " Version " ,
} ,
" Arch " : {
" fr " : " Archi " ,
" en " : " Arch " ,
} ,
" vCPU " : {
" fr " : " vCPU " ,
" en " : " vCPU " ,
} ,
" RAM (MB) " : {
" fr " : " RAM (Mo) " ,
" en " : " RAM (MB) " ,
} ,
2026-08-11 08:36:13 -04:00
" cloud-init still running after 20 min - install starts anyway "
" (it waits for cloud-init first) " : {
" fr " : " cloud-init tourne encore apres 20 min - l ' installation demarre "
" quand meme (elle attend d ' abord cloud-init) " ,
" en " : " cloud-init still running after 20 min - install starts anyway "
" (it waits for cloud-init first) " ,
} ,
" Waiting for cloud-init to finish (up to 15 min) " : {
" fr " : " Attente de la fin de cloud-init (jusqu ' a 15 min) " ,
" en " : " Waiting for cloud-init to finish (up to 15 min) " ,
} ,
" cloud-init: " : {
" fr " : " cloud-init : " ,
" en " : " cloud-init: " ,
} ,
" WARNING libvirt unreachable: the IP will not be refreshed "
" (libvirt group? re-login required) " : {
" fr " : " ATTENTION libvirt injoignable : l ' IP ne sera pas rafraichie "
" (groupe libvirt ? reconnexion necessaire) " ,
" en " : " WARNING libvirt unreachable: the IP will not be refreshed "
" (libvirt group? re-login required) " ,
} ,
" DHCP lease moved: " : {
" fr " : " bail DHCP deplace : " ,
" en " : " DHCP lease moved: " ,
} ,
[ADD] tui qemu: resume an installation already running
Installs run detached (setsid -f): closing the terminal does not stop
them, but it lost the only view onto them. With no way to resume, the
only way out was deleting the VMs and starting over.
Picking "Deploy" now looks at the latest run: if VMs there still lack an
exit marker, it offers to reopen its monitoring instead of starting
another. Time since the last write is shown, a dead run being otherwise
indistinguishable from a live one.
Only the latest run is examined: an old one left without a marker would
flag a phantom install forever. Dry-run creates nothing, so it does not
ask. "Reopen monitoring" moves to the Deployment section, where one
looks for it.
--- FR ---
Les installs partent détachées (setsid -f) : fermer le terminal ne les
arrête pas, mais faisait perdre la seule vue dessus. Sans moyen de
reprendre, la seule issue était d'effacer les VM et de recommencer.
Choisir « Déployer » regarde donc le dernier run : s'il lui reste des VM
sans marqueur de sortie, il propose de rouvrir son suivi plutôt que d'en
lancer un autre. Le silence depuis la dernière écriture est affiché, un
run mort n'étant pas distinguable autrement d'un run vivant.
Seul le dernier run est examiné : un run ancien laissé sans marqueur
signalerait éternellement une install fantôme. L'aperçu ne crée rien, il
ne pose pas la question. « Rouvrir le suivi » rejoint la section
Déploiement, où on le cherche.
Assisted-by: Claude Opus 5
2026-08-11 07:52:52 -04:00
" An install is still running: " : {
" fr " : " Une installation est encore en cours : " ,
" en " : " An install is still running: " ,
} ,
" VM(s) in progress " : {
" fr " : " VM en cours " ,
" en " : " VM(s) in progress " ,
} ,
" Last activity: " : {
" fr " : " Dernière activité il y a : " ,
" en " : " Last activity: " ,
} ,
" Reopen that monitoring " : {
" fr " : " Rouvrir ce suivi " ,
" en " : " Reopen that monitoring " ,
} ,
" Deploy anyway (new run) " : {
" fr " : " Déployer quand même (nouveau run) " ,
" en " : " Deploy anyway (new run) " ,
} ,
" Choice (number, blank = reopen): " : {
" fr " : " Choix (numéro, vide = rouvrir) : " ,
" en " : " Choice (number, blank = reopen): " ,
} ,
[ADD] tui qemu: choose mise or pyenv at deploy time
The interpreter provider is now chosen when creating a VM, in both
interfaces. "mise" installs it in the VM and lays down a precompiled
CPython; "pyenv" keeps today's build-from-source.
The question is only asked where it means something: mise publishes
binaries for amd64 and arm64 only. An all-s390x fleet never sees it, a
mixed fleet sees it along with the architectures that will fall back to
pyenv -- said before deploying, not discovered in a log an hour later.
mise goes into /usr/local/bin rather than ~/.local/bin: the remote command
runs through "ssh host 'command'", where neither ~/.profile nor ~/.bashrc
is read. Choosing "mise" exports "auto", not "mise", so a failed install
can still fall back to pyenv.
The unavailability notice was translated as "pyenv required", which said
the opposite of what happens: pyenv is the fallback, not a demand.
--- FR ---
Le fournisseur d'interpréteur se choisit désormais à la création d'une VM,
dans les deux interfaces. « mise » l'installe dans la VM et pose un
CPython précompilé ; « pyenv » garde la compilation d'aujourd'hui.
La question n'est posée que là où elle a un sens : mise ne publie de
binaires que pour amd64 et arm64. Un parc tout s390x ne la voit jamais, un
parc mixte la voit avec les architectures qui retomberont sur pyenv — dit
avant de déployer, pas découvert dans un log une heure plus tard.
mise va dans /usr/local/bin plutôt que ~/.local/bin : la commande distante
passe par « ssh hôte 'commande' », où ni ~/.profile ni ~/.bashrc ne sont
lus. Choisir « mise » exporte « auto », pas « mise », pour qu'une
installation ratée puisse encore retomber sur pyenv.
L'avis d'indisponibilité était traduit par « pyenv exigé », qui disait
l'inverse de ce qui se passe : pyenv est le repli, pas une exigence.
Assisted-by: Claude Opus 5
2026-08-11 22:11:23 -04:00
" Python interpreter: " : {
" fr " : " Interpreteur Python : " ,
" en " : " Python interpreter: " ,
} ,
" mise (precompiled, faster) " : {
" fr " : " mise (precompile, plus rapide) " ,
" en " : " mise (precompiled, faster) " ,
} ,
" pyenv (compiles from source) " : {
" fr " : " pyenv (compile depuis les sources) " ,
" en " : " pyenv (compiles from source) " ,
} ,
" Choice (number, blank = mise): " : {
" fr " : " Choix (numero, vide = mise) : " ,
" en " : " Choice (number, blank = mise): " ,
} ,
" mise has no binary for: " : {
" fr " : " mise ne publie pas de binaire pour : " ,
" en " : " mise has no binary for: " ,
} ,
" those VMs use pyenv " : {
" fr " : " ces VM utiliseront pyenv " ,
" en " : " those VMs use pyenv " ,
} ,
" Installing mise (precompiled Python) " : {
" fr " : " Installation de mise (Python precompile) " ,
" en " : " Installing mise (precompiled Python) " ,
} ,
[ADD] tui qemu: graphical VMs, with a GNOME desktop
VMs could only be servers. A server-or-graphical choice joins both
interfaces, installing GNOME along with remote access to it.
Packages come from the remote command, not cloud-init: their 1 to 2 GB
would stretch an already long boot there while leaving no trace in the
monitoring, and group installs do not go through it. The desktop
therefore does not depend on ERPLibre -- a VM may be wanted graphical
and bare.
Each distribution has its own names, taken from the source: Arch has no
xrdp in its official repositories and takes TigerVNC. The SPICE display
is set only on amd64 and arm64; s390x does expose virtio-gpu-ccw, but
nothing guarantees its kernel's DRM driver, whereas remote desktop works
everywhere.
--- FR ---
Les VM ne pouvaient être que des serveurs. Un choix serveur ou graphique
s'ajoute aux deux interfaces, et pose GNOME avec son accès distant.
Les paquets viennent de la commande distante, pas de cloud-init : leurs
1 à 2 Go y allongeraient un démarrage déjà long sans laisser de trace
dans le suivi, et les installations par groupe n'y passent pas. Le bureau
ne dépend donc pas d'ERPLibre — une VM peut être voulue graphique et nue.
Chaque distribution a ses noms, relevés à la source : Arch n'a pas xrdp
dans ses dépôts officiels et prend TigerVNC. L'écran virtuel SPICE n'est
posé que sur amd64 et arm64 ; s390x expose bien virtio-gpu-ccw, mais rien
ne garantit le pilote DRM de son noyau, alors que le bureau distant, lui,
marche partout.
Assisted-by: Claude Opus 5
2026-08-11 09:49:17 -04:00
" VM type: " : {
" fr " : " Type de VM : " ,
" en " : " VM type: " ,
} ,
" Server (no graphical interface) " : {
" fr " : " Serveur (sans interface graphique) " ,
" en " : " Server (no graphical interface) " ,
} ,
" Graphical (server + GNOME desktop) " : {
" fr " : " Graphique (serveur + bureau GNOME) " ,
" en " : " Graphical (server + GNOME desktop) " ,
} ,
[UPD] tui qemu: the plan shows each VM type
The server-or-desktop choice only showed on the left, in the form. The
table on the right, the one read before launching, said nothing about it:
two visually identical plans could produce different VMs.
The Status column, empty until now for a VM to create, therefore carries
the type, and the totals line repeats it. An already defined VM keeps its
collision message: it is not touched, showing a desktop there would
suggest one is about to be installed on it.
--- FR ---
Le choix serveur ou bureau ne se voyait qu'à gauche, dans le formulaire.
Le tableau de droite, celui qu'on relit avant de lancer, n'en disait
rien : deux plans identiques à l'écran pouvaient produire des VM
différentes.
La colonne Statut, vide jusqu'ici pour une VM à créer, porte donc le
type, et la ligne de totaux le rappelle. Une VM déjà définie garde son
message de collision : elle n'est pas retouchée, lui afficher un bureau
laisserait croire qu'on va lui en poser un.
Assisted-by: Claude Opus 5
2026-08-11 18:55:52 -04:00
" server " : {
" fr " : " serveur " ,
" en " : " server " ,
} ,
[UPD] support: drop Ubuntu 20.04/22.04, add AlmaLinux and Rocky
Ubuntu 20.04 and 22.04 leave EVERY architecture, not just s390x. pikepdf
needs qpdf 12.2, whose build requires C++20, while focal ships GCC 9 and
publishes no g++-10 for s390x at all. Python 3.8, node 10, cargo 0.67 and
OpenSSL 1.1.1 each had a workaround; the pile of them did not. 18.04
follows, already off the lists. The refusal lands before any apt, this
script also serving existing machines.
AlmaLinux 9 and 10, Rocky 9 and 10 join the catalog on all four
architectures: the twelve "latest" URLs were opened, with no index to
parse unlike Fedora. They would have booted unreachable though -- the
cloud-config forced "groups: users, sudo", but the RHEL family has no
sudo group, only wheel, and an unknown group makes useradd fail, hence no
password and no key. The very trap already known for Debian, repeated
elsewhere. Host side, EPEL and CRB are enabled: without them most -devel
packages are missing, silently.
The server / graphical choice gains Cinnamon, the Linux Mint desktop,
from the distribution's own repositories. Mint's repository is set aside:
plain HTTP, and i386/amd64 only, which would rule out arm64 and s390x.
Along the way, dnf now installs an ENVIRONMENT rather than a group --
"gnome-desktop" brings gdm and gnome-shell but not base-x, hence no X
server.
--- FR ---
Ubuntu 20.04 et 22.04 partent de TOUTES les architectures, pas seulement
de s390x. pikepdf réclame qpdf 12.2, dont la compilation exige C++20,
quand focal livre GCC 9 et ne publie même pas de g++-10 pour s390x.
Python 3.8, node 10, cargo 0.67 et OpenSSL 1.1.1 avaient chacun leur
contournement ; leur accumulation, non. 18.04 suit, déjà hors des
listes. Le refus tombe avant tout apt, ce script servant aussi les
machines existantes.
AlmaLinux 9 et 10, Rocky 9 et 10 entrent au catalogue, sur les quatre
architectures : les douze URL « latest » ont été ouvertes, aucun index à
analyser contrairement à Fedora. Elles auraient pourtant démarré
inaccessibles — le cloud-config imposait « groups: users, sudo », or la
famille RHEL n'a pas de groupe sudo mais wheel, et un groupe inconnu fait
échouer useradd, donc ni mot de passe ni clé. C'est le piège déjà connu
pour Debian, reproduit ailleurs. Côté hôte, EPEL et CRB sont activés :
sans eux la plupart des -devel manquent, en silence.
Le choix serveur / graphique gagne Cinnamon, le bureau de Linux Mint,
depuis les dépôts de la distribution. Le dépôt de Mint lui-même est
écarté : il est en HTTP nu et ne publie que i386 et amd64, ce qui
exclurait arm64 et s390x. Au passage, dnf installe désormais un
ENVIRONNEMENT et non un groupe — « gnome-desktop » apporte gdm et
gnome-shell mais pas base-x, donc pas de serveur X.
Assisted-by: Claude Opus 5
2026-08-11 18:46:11 -04:00
" Graphical (server + desktop): " : {
" fr " : " Graphique (serveur + bureau) : " ,
" en " : " Graphical (server + desktop): " ,
} ,
" Installing the desktop (long): " : {
" fr " : " Installation du bureau (long) : " ,
" en " : " Installing the desktop (long): " ,
} ,
[ADD] tui qemu: graphical VMs, with a GNOME desktop
VMs could only be servers. A server-or-graphical choice joins both
interfaces, installing GNOME along with remote access to it.
Packages come from the remote command, not cloud-init: their 1 to 2 GB
would stretch an already long boot there while leaving no trace in the
monitoring, and group installs do not go through it. The desktop
therefore does not depend on ERPLibre -- a VM may be wanted graphical
and bare.
Each distribution has its own names, taken from the source: Arch has no
xrdp in its official repositories and takes TigerVNC. The SPICE display
is set only on amd64 and arm64; s390x does expose virtio-gpu-ccw, but
nothing guarantees its kernel's DRM driver, whereas remote desktop works
everywhere.
--- FR ---
Les VM ne pouvaient être que des serveurs. Un choix serveur ou graphique
s'ajoute aux deux interfaces, et pose GNOME avec son accès distant.
Les paquets viennent de la commande distante, pas de cloud-init : leurs
1 à 2 Go y allongeraient un démarrage déjà long sans laisser de trace
dans le suivi, et les installations par groupe n'y passent pas. Le bureau
ne dépend donc pas d'ERPLibre — une VM peut être voulue graphique et nue.
Chaque distribution a ses noms, relevés à la source : Arch n'a pas xrdp
dans ses dépôts officiels et prend TigerVNC. L'écran virtuel SPICE n'est
posé que sur amd64 et arm64 ; s390x expose bien virtio-gpu-ccw, mais rien
ne garantit le pilote DRM de son noyau, alors que le bureau distant, lui,
marche partout.
Assisted-by: Claude Opus 5
2026-08-11 09:49:17 -04:00
" Choice (number, blank = server): " : {
" fr " : " Choix (numero, vide = serveur) : " ,
" en " : " Choice (number, blank = server): " ,
} ,
" Installing the GNOME desktop (long) " : {
" fr " : " Installation du bureau GNOME (long) " ,
" en " : " Installing the GNOME desktop (long) " ,
} ,
[ADD] install: openSUSE support, mirrors and packages
Tumbleweed is the only catalog entry whose qpdf already clears the pikepdf
threshold: 12.3.2 against 12.2 required, so the half-hour qpdf build under
s390x emulation never runs there. It is also the only family foreign to
both RHEL and Debian, and it brings zypper, absent from the repository:
wired into the install_dev.sh dispatch, a dependency script, the remote
bootstrap and the desktop block.
Four SUSE traps, all met on real machines. Global options precede the
subcommand, and misplaced ones abort the call; without
"--auto-agree-with-licenses" zypper waits for an answer nobody gives in a
detached install. A compat package that PROVIDES another under a different
name makes zypper raise a conflict and drop the whole batch, so only what
nothing already provides is requested. pkg-config no longer exists as an
RPM, only as a capability. And a single mirror, unreachable one day, sent
everyone back to Europe -- three are probed in order now.
CentOS Stream is dropped again: it served as a canary but 10 duplicates
what Alma and Rocky already cover.
--- FR ---
Tumbleweed est la seule entrée du catalogue dont qpdf franchit déjà le
seuil de pikepdf : 12.3.2 contre 12.2 exigé, si bien que la demi-heure de
compilation de qpdf sous émulation s390x ne s'y déclenche jamais. C'est
aussi la seule famille étrangère à RHEL comme à Debian, et elle apporte
zypper, absent du dépôt : câblé dans l'aiguillage d'install_dev.sh, un
script de dépendances, l'amorçage distant et le bloc bureau.
Quatre pièges SUSE, tous rencontrés sur machine réelle. Les options
globales précèdent la sous-commande, mal placées elles arrêtent l'appel ;
sans « --auto-agree-with-licenses » zypper attend une réponse que personne
ne donne dans une installation détachée. Un paquet compat qui FOURNIT un
autre sous un nom différent fait lever un conflit à zypper, qui abandonne
le lot entier : on ne demande donc que ce que rien ne fournit déjà.
pkg-config n'existe plus comme RPM, seulement comme capacité. Et un miroir
unique, injoignable un jour, renvoyait tout le monde en Europe — trois
sont désormais sondés dans l'ordre.
CentOS Stream repart : il servait de canari, mais 10 double ce qu'Alma et
Rocky couvrent déjà.
Assisted-by: Claude Opus 5
2026-08-11 20:38:12 -04:00
" openSUSE mirror: " : {
" fr " : " miroir openSUSE : " ,
" en " : " openSUSE mirror: " ,
} ,
[ADD] tui qemu: graphical VMs, with a GNOME desktop
VMs could only be servers. A server-or-graphical choice joins both
interfaces, installing GNOME along with remote access to it.
Packages come from the remote command, not cloud-init: their 1 to 2 GB
would stretch an already long boot there while leaving no trace in the
monitoring, and group installs do not go through it. The desktop
therefore does not depend on ERPLibre -- a VM may be wanted graphical
and bare.
Each distribution has its own names, taken from the source: Arch has no
xrdp in its official repositories and takes TigerVNC. The SPICE display
is set only on amd64 and arm64; s390x does expose virtio-gpu-ccw, but
nothing guarantees its kernel's DRM driver, whereas remote desktop works
everywhere.
--- FR ---
Les VM ne pouvaient être que des serveurs. Un choix serveur ou graphique
s'ajoute aux deux interfaces, et pose GNOME avec son accès distant.
Les paquets viennent de la commande distante, pas de cloud-init : leurs
1 à 2 Go y allongeraient un démarrage déjà long sans laisser de trace
dans le suivi, et les installations par groupe n'y passent pas. Le bureau
ne dépend donc pas d'ERPLibre — une VM peut être voulue graphique et nue.
Chaque distribution a ses noms, relevés à la source : Arch n'a pas xrdp
dans ses dépôts officiels et prend TigerVNC. L'écran virtuel SPICE n'est
posé que sur amd64 et arm64 ; s390x expose bien virtio-gpu-ccw, mais rien
ne garantit le pilote DRM de son noyau, alors que le bureau distant, lui,
marche partout.
Assisted-by: Claude Opus 5
2026-08-11 09:49:17 -04:00
" Remote desktop: " : {
" fr " : " Bureau distant : " ,
" en " : " Remote desktop: " ,
} ,
2026-08-11 08:59:10 -04:00
" One run per install " : {
" fr " : " une execution par installation " ,
" en " : " One run per install " ,
} ,
" limit to host cores " : {
" fr " : " limiter au nombre de coeurs de l ' hote " ,
" en " : " limit to host cores " ,
} ,
[ADD] tui qemu: free values for vCPU, RAM and disk
The CLI already took a typed value -- a letter picks a suggestion, a
digit stands for itself. The form, though, locked the custom profile
into its three dropdowns.
Each now gets a final "free value..." entry revealing an input below it.
An invalid entry overwrites nothing: the resource falls back to the
catalog value instead of freezing the view.
The 3 vCPU preset was missing too, between 2 and 4; it comes from the
same constant, so both interfaces gain it together.
--- FR ---
La CLI acceptait déjà une valeur tapée — une lettre choisit une
suggestion, un chiffre vaut pour lui-même. Le formulaire, lui, enfermait
le profil personnalisé dans ses trois listes déroulantes.
Chacune reçoit donc un dernier choix, « valeur libre… », qui révèle une
saisie sous elle. Une entrée invalide n'écrase rien : la ressource
retombe sur celle du catalogue plutôt que de bloquer la vue.
Le préréglage 3 vCPU manquait aussi, entre 2 et 4 ; il vient de la même
constante, donc les deux interfaces l'offrent ensemble.
Assisted-by: Claude Opus 5
2026-08-11 06:57:49 -04:00
" free value… " : {
" fr " : " valeur libre… " ,
" en " : " free value… " ,
} ,
" Disk (e.g. 250G, 1.5T) " : {
" fr " : " Disque (ex. 250G, 1,5T) " ,
" en " : " Disk (e.g. 250G, 1.5T) " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" all archs " : {
" fr " : " toutes " ,
" en " : " all " ,
} ,
" Tick what to deploy " : {
" fr " : " Cocher ce qu ' on veut déployer " ,
" en " : " Tick what to deploy " ,
} ,
" F7 main versions · F6 all " : {
" fr " : " F7 versions principales · F6 tout " ,
" en " : " F7 main versions · F6 all " ,
} ,
" exists - skipped " : {
" fr " : " existe — ignorée " ,
" en " : " exists - skipped " ,
} ,
" orphan disk - will FAIL " : {
" fr " : " disque orphelin — ÉCHOUERA " ,
" en " : " orphan disk - will FAIL " ,
} ,
" press F5 again to confirm " : {
" fr " : " F5 à nouveau pour confirmer " ,
" en " : " press F5 again to confirm " ,
} ,
" Deploy " : {
" fr " : " Déployer " ,
" en " : " Deploy " ,
} ,
" Edit VM " : {
" fr " : " Éditer la VM " ,
" en " : " Edit VM " ,
} ,
[ADD] tui qemu: set every VM in place, type included
VM type was global: the whole fleet as servers, or all of them GNOME. It
now lives on the VM, all the way down -- the creation flag, and one remote
command per machine at install time, where a single one served them all.
The right pane is no longer a table but a row of widgets per VM: vCPU,
RAM, disk and type, each with its usual values and a free entry. The
left-hand fields become the shared default, which the screen now says. The
scope selector and the F2 modal go away: given two ways to do the same
thing, keep the visible one.
Three traps came out of it, and the tests lock them. Widget ids carry a
RANK, and the rank shifts when an entry is ticked, so an event from an
already destroyed widget applied to the VM that took its place -- rows now
carry a generation, marked BEFORE mounting, since mount_all empties the
pending children and marking after it was a race. The x1..x4 profile no
longer reached any VM. And a total of zero never said it had counted
nothing.
--- FR ---
Le type de VM était global : tout le parc en serveur, ou tout en GNOME. Il
vit désormais sur la VM, jusqu'au bout — le drapeau de création, et une
commande distante par machine à l'installation, là où une seule les
servait toutes.
Le panneau de droite n'est plus un tableau mais une rangée de widgets par
VM : vCPU, RAM, disque et type, chacun avec ses valeurs usuelles et une
saisie libre. Les champs de gauche deviennent le défaut commun, ce que
l'écran dit maintenant. Le sélecteur de portée et la modale F2 partent :
entre deux façons de faire la même chose, on garde la visible.
Trois pièges en sont sortis, et les tests les verrouillent. Les
identifiants de widgets portent un RANG, et le rang se décale quand on
coche une entrée : un événement émis par un widget déjà détruit
s'appliquait à la VM qui avait pris sa place — les rangées portent
maintenant une génération, marquée AVANT le montage, car mount_all vide
les enfants en attente et marquer après était une course. Le profil x1..x4
n'atteignait plus aucune VM. Et un total à zéro ne disait pas qu'il
n'avait rien compté.
Assisted-by: Claude Opus 5
2026-08-12 02:46:41 -04:00
" Server " : {
" fr " : " Serveur " ,
" en " : " Server " ,
} ,
" VM type (default): " : {
" fr " : " Type de VM (défaut) : " ,
" en " : " VM type (default): " ,
} ,
" These values are the default for every VM; " : {
" fr " : " Ces valeurs sont le défaut de chaque VM ; " ,
" en " : " These values are the default for every VM; " ,
} ,
" adjust any of them per VM on the right. " : {
" fr " : " ajustez-les VM par VM dans la vue de droite. " ,
" en " : " adjust any of them per VM on the right. " ,
} ,
" Application store: " : {
" fr " : " Magasin d ' applications : " ,
" en " : " Application store: " ,
} ,
" Application store (graphical Ubuntu VMs): " : {
" fr " : " Magasin d ' applications (VM Ubuntu graphiques) : " ,
" en " : " Application store (graphical Ubuntu VMs): " ,
} ,
" deb only (epiphany-browser) " : {
" fr " : " deb uniquement (epiphany-browser) " ,
" en " : " deb only (epiphany-browser) " ,
} ,
" Flatpak tooling, no Flathub " : {
" fr " : " outillage Flatpak, sans Flathub " ,
" en " : " Flatpak tooling, no Flathub " ,
} ,
" snap (Ubuntu default, Firefox) " : {
" fr " : " snap (défaut Ubuntu, Firefox) " ,
" en " : " snap (Ubuntu default, Firefox) " ,
} ,
" snap needs the store; slow under emulation. " : {
" fr " : " snap exige le store ; lent sous émulation. " ,
" en " : " snap needs the store; slow under emulation. " ,
} ,
" No graphical VM on a snap-based distro. " : {
" fr " : " Aucune VM graphique sur une distribution à snap. " ,
" en " : " No graphical VM on a snap-based distro. " ,
} ,
" Resources — applied to ALL VMs " : {
" fr " : " Ressources — appliquées à TOUTES les VM " ,
" en " : " Resources — applied to ALL VMs " ,
} ,
" The profile and these fields change EVERY VM. " : {
" fr " : " Le profil et ces champs changent TOUTES les VM. " ,
" en " : " The profile and these fields change EVERY VM. " ,
} ,
" A VM edited on the right (marked) keeps its own. " : {
" fr " : " Une VM modifiée à droite (marquée ✎) garde les siennes. " ,
" en " : " A VM edited on the right (marked) keeps its own. " ,
} ,
" Rename the VM " : {
" fr " : " Renommer la VM " ,
" en " : " Rename the VM " ,
} ,
" Rename " : {
" fr " : " Renommer " ,
" en " : " Rename " ,
} ,
" Empty = back to the automatic name: " : {
" fr " : " Vide = revenir au nom automatique : " ,
" en " : " Empty = back to the automatic name: " ,
} ,
" Invalid name: letters, digits, hyphens. " : {
" fr " : " Nom invalide : lettres, chiffres et traits d ' union. " ,
" en " : " Invalid name: letters, digits, hyphens. " ,
} ,
" default; a VM may differ, see its line " : {
" fr " : " défaut ; une VM peut s ' en écarter, voir sa ligne " ,
" en " : " default; a VM may differ, see its line " ,
} ,
" varies, see each line " : {
" fr " : " varie, voir chaque ligne " ,
" en " : " varies, see each line " ,
} ,
" From your workstation: " : {
" fr " : " Depuis votre poste : " ,
" en " : " From your workstation: " ,
} ,
" then point your client at " : {
" fr " : " puis pointez votre client sur " ,
" en " : " then point your client at " ,
} ,
" Remote desktop tunnel (VNC/RDP through SSH) " : {
" fr " : " 🖥 Tunnel bureau distant (VNC/RDP par SSH) " ,
" en " : " 🖥 Remote desktop tunnel (VNC/RDP through SSH) " ,
} ,
" Remote desktop tunnel " : {
" fr " : " Tunnel vers le bureau distant " ,
" en " : " Remote desktop tunnel " ,
} ,
" Which VM? " : { " fr " : " Quelle VM ? " , " en " : " Which VM? " } ,
" No IP for this VM; is it running? " : {
" fr " : " Pas d ' IP pour cette VM ; tourne-t-elle ? " ,
" en " : " No IP for this VM; is it running? " ,
} ,
" Run this on YOUR workstation: " : {
" fr " : " À lancer sur VOTRE poste : " ,
" en " : " Run this on YOUR workstation: " ,
} ,
[ADD] script todo: ouvrir l'écran d'une VM avec virt-viewer
C'est la voie la plus courte : virt-viewer parle à libvirt par « qemu+ssh », lit
le port de l'écran par libvirt et monte SON tunnel — aucun « ssh -L » à tenir
ouvert, rien à deviner. Le menu tunnel le propose donc en cinquième choix.
La seule question qui compte est celle de l'affichage, et c'est l'environnement
qui tranche, pas une question de plus. Un affichage présent (poste, ou ssh -X) :
virt-viewer est installé s'il manque — apt, dnf, pacman ou zypper — puis lancé
détaché. Aucun affichage : la commande est donnée pour le poste, et rien n'est
installé sur une machine sans écran.
--- EN ---
The shortest path: virt-viewer talks to libvirt over "qemu+ssh", reads the
display port from libvirt and builds its OWN tunnel — no "ssh -L" to keep open,
nothing to guess. The tunnel menu offers it as a fifth choice.
The only question that matters is the display, and the environment answers it
rather than one more prompt. A display present (workstation, or ssh -X):
virt-viewer gets installed if missing — apt, dnf, pacman or zypper — then
launched detached. No display: the command is handed over for the workstation,
and nothing is installed on a screenless machine.
Assisted-by: Claude Opus 5
2026-08-20 03:33:11 -04:00
" Graphical console (virt-viewer, built-in tunnel) " : {
" fr " : " Console graphique (virt-viewer, tunnel intégré) " ,
" en " : " Graphical console (virt-viewer, built-in tunnel) " ,
} ,
" virt-viewer is missing here; installing it. " : {
" fr " : " virt-viewer manque ici ; installation. " ,
" en " : " virt-viewer is missing here; installing it. " ,
} ,
" no known package manager here. " : {
" fr " : " aucun gestionnaire de paquets connu ici. " ,
" en " : " no known package manager here. " ,
} ,
" virt-viewer still missing after the install. " : {
" fr " : " virt-viewer manque toujours après l ' installation. " ,
" en " : " virt-viewer still missing after the install. " ,
} ,
" Opening " : { " fr " : " Ouverture " , " en " : " Opening " } ,
" Window opening on your display " : {
" fr " : " Fenêtre en ouverture sur votre affichage " ,
" en " : " Window opening on your display " ,
} ,
" Log: " : { " fr " : " Journal : " , " en " : " Log: " } ,
" No display here; run this on YOUR workstation: " : {
" fr " : " Aucun affichage ici ; à lancer sur VOTRE poste : " ,
" en " : " No display here; run this on YOUR workstation: " ,
} ,
[FIX] proxmox : le rebond est le seul chemin vers la VM
Une VM créée sur l'hôte Proxmox vit derrière lui, sur un pont interne : son
adresse n'est pas routable d'ici, et seule l'entrée ~/.ssh/config avec son
ProxyJump y mène. Décocher « ajouter une entrée » tout en demandant une
installation laissait donc le suivi frapper à une porte qui n'existe pas.
L'entrée est maintenant écrite quand une installation ou le suivi la
réclame, et on le dit. Sans rien à faire dans la VM, le choix est respecté.
Trouvé par l'audit du découpage, pas par l'exécution : c'est un cas que
personne n'avait joué.
--- EN ---
A VM created on the Proxmox host lives behind it, on an internal bridge: its
address is not routable from here, and only the ~/.ssh/config entry with its
ProxyJump leads there. Unticking "add an entry" while asking for an install
therefore left the monitor knocking at a door that does not exist. The entry
is now written whenever an install or the dashboard needs it, and we say so.
With nothing to do inside the VM, the choice stands.
Found by the split's audit, not by running it: nobody had played that case.
Assisted-by: Claude Opus 5
2026-08-24 04:21:45 -04:00
" ~/.ssh/config written anyway (install) " : {
" fr " : " entrée ~/.ssh/config écrite quand même : l ' installation passe par le rebond " ,
" en " : " ~/.ssh/config written anyway: the install needs the jump " ,
} ,
[ADD] migration: retirer les index qu'Odoo 17 a créés en double
`make_index_name` rend `{table}__{colonne}_index` depuis la 17 — deux
soulignés. Le nouvel index est créé, l'ancien reste. Mesuré sur une
chaîne 12 → 18 : 1 paire en 12, 3 en 16, 370 en 17, 365 en 18. Inerte à
la lecture, coûteux à l'écriture — deux arbres B entretenus à chaque
INSERT — et c'est le seul défaut de la famille qui empire tout seul.
L'outil s'abstient plus qu'il ne supprime : contrainte, clé primaire,
index partiel ou calculé, méthode ou classe d'opérateurs différentes,
convention non reconnaissable. Sur la base réelle, 367 sûrs sur 376.
Éprouvé sur une copie : 364 retirés, 10 Mo libérés, les 6301 contraintes
identiques au nom près, Odoo charge sans une ligne de journal, et un
« -u all » complet n'en recrée aucun.
--- EN ---
`make_index_name` returns `{table}__{column}_index` since 17 — two
underscores. The new index is created, the old one stays. Measured on a
12 → 18 chain: 1 pair in 12, 3 in 16, 370 in 17, 365 in 18. Inert on
read, costly on write — two B-trees maintained per INSERT — and the only
defect of this family that worsens on its own.
The tool abstains more than it drops: constraint, primary key, partial or
computed index, differing access method or operator class, unrecognisable
convention. On the real database, 367 safe out of 376.
Proven on a copy: 364 dropped, 10 MB freed, the 6301 constraints
identical name for name, Odoo loads without a single log line, and a full
« -u all » recreates none of them.
Assisted-by: Claude Opus 5
2026-08-24 04:02:27 -04:00
" Still on the distribution kernel: " : {
" fr " : " Encore sur le noyau de la distribution : " ,
" en " : " Still on the distribution kernel: " ,
} ,
" Reboot the host: no bridge, no NAT until then. " : {
" fr " : " Redémarrer l ' hôte : ni pont ni NAT avant cela. " ,
" en " : " Reboot the host: no bridge, no NAT until then. " ,
} ,
[ADD] script todo: ouvrir l'écran d'une VM avec virt-viewer
C'est la voie la plus courte : virt-viewer parle à libvirt par « qemu+ssh », lit
le port de l'écran par libvirt et monte SON tunnel — aucun « ssh -L » à tenir
ouvert, rien à deviner. Le menu tunnel le propose donc en cinquième choix.
La seule question qui compte est celle de l'affichage, et c'est l'environnement
qui tranche, pas une question de plus. Un affichage présent (poste, ou ssh -X) :
virt-viewer est installé s'il manque — apt, dnf, pacman ou zypper — puis lancé
détaché. Aucun affichage : la commande est donnée pour le poste, et rien n'est
installé sur une machine sans écran.
--- EN ---
The shortest path: virt-viewer talks to libvirt over "qemu+ssh", reads the
display port from libvirt and builds its OWN tunnel — no "ssh -L" to keep open,
nothing to guess. The tunnel menu offers it as a fifth choice.
The only question that matters is the display, and the environment answers it
rather than one more prompt. A display present (workstation, or ssh -X):
virt-viewer gets installed if missing — apt, dnf, pacman or zypper — then
launched detached. No display: the command is handed over for the workstation,
and nothing is installed on a screenless machine.
Assisted-by: Claude Opus 5
2026-08-20 03:33:11 -04:00
" A ~/.ssh/config alias works there too. " : {
" fr " : " Un alias de ~/.ssh/config y fonctionne aussi. " ,
" en " : " A ~/.ssh/config alias works there too. " ,
} ,
" It builds its own tunnel; no ssh -L to keep open. " : {
" fr " : " Il monte son propre tunnel ; aucun ssh -L à tenir ouvert. " ,
" en " : " It builds its own tunnel; no ssh -L to keep open. " ,
} ,
" Missing? Install virt-viewer: " : {
" fr " : " Absent ? Installez virt-viewer : " ,
" en " : " Missing? Install virt-viewer: " ,
} ,
[FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier
Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK
était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent
désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver
chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du
contenu. Le côté application est dans le dépôt mobile ; ce commit porte la
vérification et retire le contournement.
Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées,
et 20 fichiers relus depuis les packs identiques octet pour octet à leur source.
L'installation le vérifie et échoue sinon : une application qui ne porte pas le
code qu'elle doit montrer n'est pas celle demandée.
--- EN ---
The stopgap has served its time: the repositories were not embedded at all, and
the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now
enter as packs — 4 MB slices and one index per repository saying where each file
lives — which brings the count to 391 entries without losing any content. The
app side lives in the mobile repository; this commit carries the verification
and drops the workaround.
Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002
entries, and 20 files read back from the packs identical byte for byte to their
source. The install verifies it and fails otherwise: an app that does not carry
the code it must show is not the one that was asked for.
Assisted-by: Claude Opus 5
2026-08-20 03:51:14 -04:00
" repo transfer into the app " : {
" fr " : " transfert des dépôts dans l ' application " ,
" en " : " repo transfer into the app " ,
} ,
[FIX] script todo: démarrer le bureau, pas seulement l'activer
Une VM graphique restait sur une console texte jusqu'au premier redémarrage.
GNOME installé, gdm3 installé, graphical.target par défaut, lien
display-manager.service posé par le paquet — et rien à l'écran. Deux causes
superposées, mesurées sur erplibre-ubuntu-2604-gnome :
graphical.target était DÉJÀ atteinte quand le paquet est arrivé, et une cible
active ne rattrape pas un service ajouté après coup. Et « systemctl enable gdm »
rend 0 sans rien faire sur Debian et Ubuntu : l'unité n'a pas de « WantedBy »,
seulement l'alias que le paquet pose lui-même.
Le bureau est donc démarré, avec repli sur le service de la saveur, et l'échec
se dit au lieu de se taire. Vérifié : bureau arrêté puis fragment rejoué ->
gnome-shell revient, écran de connexion GDM à l'image.
--- EN ---
A graphical VM stayed on a text console until its first reboot. GNOME
installed, gdm3 installed, graphical.target the default, the
display-manager.service alias in place by the package — and nothing on screen.
Two causes stacked, measured on erplibre-ubuntu-2604-gnome:
graphical.target had ALREADY been reached when the package arrived, and an
active target does not pick up a service added afterwards. And "systemctl enable
gdm" returns 0 doing nothing on Debian and Ubuntu: the unit has no "WantedBy",
only the alias the package installs itself.
The desktop is therefore started, with a fallback to the flavour's service, and
a failure says so instead of staying quiet. Verified: desktop stopped then the
fragment replayed -> gnome-shell back, GDM greeter on screen.
Assisted-by: Claude Opus 5
2026-08-20 02:56:21 -04:00
" graphical session started " : {
" fr " : " session graphique démarrée " ,
" en " : " graphical session started " ,
} ,
" graphical session not started; reboot the VM " : {
" fr " : " session graphique non démarrée ; redémarrer la VM " ,
" en " : " graphical session not started; reboot the VM " ,
} ,
2026-08-20 02:40:16 -04:00
" Hypervisor console (QEMU screen, no guest server) " : {
" fr " : " Console de l ' hyperviseur (écran QEMU, rien dans l ' invitée) " ,
" en " : " Hypervisor console (QEMU screen, no guest server) " ,
} ,
" then point your VNC client at " : {
" fr " : " puis pointez votre client VNC sur " ,
" en " : " then point your VNC client at " ,
} ,
" Target is the hypervisor " : {
" fr " : " La cible est l ' hyperviseur " ,
" en " : " Target is the hypervisor " ,
} ,
" not the VM: the socket is QEMU-side. " : {
" fr " : " et non la VM : le socket est du côté de QEMU. " ,
" en " : " not the VM: the socket is QEMU-side. " ,
} ,
[FIX] proxmox : quatre écrans qui parlaient d'une machine locale
La confirmation de suppression promettait à TOUTE VM « son disque qcow2
EFFACÉ », puis nommait /var/lib/libvirt/images/<nom>.qcow2. Sur une VM
Proxmox ce fichier n'existe pas — au mieux, au pire c'est celui d'une autre
VM du même nom. C'est la peur exacte qui avait fait remonter le nettoyage.
Elle nomme désormais l'hôte, le VMID et « qm destroy ».
« Console de l'hyperviseur » lisait le port par « virsh vncdisplay ». Un
Proxmox n'a pas de libvirt : l'échec se lisait « écran fermé » et on
conseillait « sudo virsh edit » sur une machine sans ce binaire. Ce n'est pas
un écran fermé, c'est la mauvaise question — Proxmox sert le sien par un
ticket. Les deux vrais chemins sont nommés : la console série, l'interface
web par tunnel.
La colonne Odoo était un 🟢 acquis pour toujours : « Odoo ne redescend pas en
cours d'install » est faux — le service redémarre au moins une fois, et il
lui arrive de mourir. Elle est relue, gratuitement sur Proxmox, toutes les
trente secondes ailleurs. Un hôte muet reste distinct d'un Odoo tombé.
Enfin « Versions principales » (F7) manquait à l'écran Proxmox, qui affiche
pourtant le même catalogue. Les trois gestes du catalogue vivent maintenant
dans le socle du plan, où ils ne peuvent plus diverger.
--- EN ---
The delete confirmation promised EVERY VM "its qcow2 disk ERASED", then named
/var/lib/libvirt/images/<name>.qcow2. On a Proxmox VM that file does not
exist — at best; at worst it is another VM's, of the same name. That is the
very fear that surfaced the cleanup report. It now names the host, the VMID
and "qm destroy".
"Hypervisor console" read the port through "virsh vncdisplay". Proxmox has no
libvirt: the failure read as "screen closed" and we advised "sudo virsh edit"
on a machine without that binary. It is not a closed screen, it is the wrong
question — Proxmox serves its own by ticket. Both real paths are named: the
serial console, the web interface through a tunnel.
The Odoo column was a 🟢 acquired forever: "Odoo does not go back down during
the install" is false — the service restarts at least once, and it does die.
It is re-read, free on Proxmox, every thirty seconds elsewhere. A silent host
stays distinct from a dead Odoo.
Finally "Main versions" (F7) was missing from the Proxmox screen, which shows
the same catalog. The catalog's three gestures now live in the plan
foundation, where they can no longer diverge.
Assisted-by: Claude Opus 5
2026-08-24 23:07:55 -04:00
" This hypervisor is Proxmox VE: it has no libvirt. " : {
" fr " : " Cet hyperviseur est un Proxmox VE : il n ' a pas de libvirt. " ,
" en " : " This hypervisor is Proxmox VE: it has no libvirt. " ,
} ,
" Its screen is served by a ticket, not by a VNC port. " : {
" fr " : " Son écran est servi par un ticket, pas par un port VNC. " ,
" en " : " Its screen is served by a ticket, not by a VNC port. " ,
} ,
" Two ways in: " : {
" fr " : " Deux chemins : " ,
" en " : " Two ways in: " ,
} ,
" the serial console, VMID from the Proxmox menu: " : {
" fr " : " la console série, le VMID venant du menu Proxmox : " ,
" en " : " the serial console, VMID from the Proxmox menu: " ,
} ,
" the web interface, through a tunnel: " : {
" fr " : " l ' interface web, par un tunnel : " ,
" en " : " the web interface, through a tunnel: " ,
} ,
" TODO > Execute > Deploy > Proxmox VE does the first. " : {
" fr " : " TODO > Execute > Deploy > Proxmox VE fait le premier. " ,
" en " : " TODO > Execute > Deploy > Proxmox VE does the first. " ,
} ,
2026-08-20 02:40:16 -04:00
" This VM exposes no VNC port. " : {
" fr " : " Cette VM n ' expose aucun port VNC. " ,
" en " : " This VM exposes no VNC port. " ,
} ,
" Its display is likely spice with listen=none: " : {
" fr " : " Son affichage est probablement spice avec listen=none : " ,
" en " : " Its display is likely spice with listen=none: " ,
} ,
" To open it on the loopback (VM restart required): " : {
" fr " : " Pour l ' ouvrir sur la boucle locale (redémarrage de la VM "
" requis) : " ,
" en " : " To open it on the loopback (VM restart required): " ,
} ,
" New VMs get this by default; see deploy_qemu. " : {
" fr " : " Les VM neuves l ' ont par défaut ; voir deploy_qemu. " ,
" en " : " New VMs get this by default; see deploy_qemu. " ,
} ,
" No ProxyJump for this host in ~/.ssh/config. " : {
" fr " : " Aucun ProxyJump pour cet hôte dans ~/.ssh/config. " ,
" en " : " No ProxyJump for this host in ~/.ssh/config. " ,
} ,
" Cannot tell which machine runs its QEMU. " : {
" fr " : " Impossible de savoir quelle machine fait tourner son QEMU. " ,
" en " : " Cannot tell which machine runs its QEMU. " ,
} ,
[ADD] tui qemu: set every VM in place, type included
VM type was global: the whole fleet as servers, or all of them GNOME. It
now lives on the VM, all the way down -- the creation flag, and one remote
command per machine at install time, where a single one served them all.
The right pane is no longer a table but a row of widgets per VM: vCPU,
RAM, disk and type, each with its usual values and a free entry. The
left-hand fields become the shared default, which the screen now says. The
scope selector and the F2 modal go away: given two ways to do the same
thing, keep the visible one.
Three traps came out of it, and the tests lock them. Widget ids carry a
RANK, and the rank shifts when an entry is ticked, so an event from an
already destroyed widget applied to the VM that took its place -- rows now
carry a generation, marked BEFORE mounting, since mount_all empties the
pending children and marking after it was a race. The x1..x4 profile no
longer reached any VM. And a total of zero never said it had counted
nothing.
--- FR ---
Le type de VM était global : tout le parc en serveur, ou tout en GNOME. Il
vit désormais sur la VM, jusqu'au bout — le drapeau de création, et une
commande distante par machine à l'installation, là où une seule les
servait toutes.
Le panneau de droite n'est plus un tableau mais une rangée de widgets par
VM : vCPU, RAM, disque et type, chacun avec ses valeurs usuelles et une
saisie libre. Les champs de gauche deviennent le défaut commun, ce que
l'écran dit maintenant. Le sélecteur de portée et la modale F2 partent :
entre deux façons de faire la même chose, on garde la visible.
Trois pièges en sont sortis, et les tests les verrouillent. Les
identifiants de widgets portent un RANG, et le rang se décale quand on
coche une entrée : un événement émis par un widget déjà détruit
s'appliquait à la VM qui avait pris sa place — les rangées portent
maintenant une génération, marquée AVANT le montage, car mount_all vide
les enfants en attente et marquer après était une course. Le profil x1..x4
n'atteignait plus aucune VM. Et un total à zéro ne disait pas qu'il
n'avait rien compté.
Assisted-by: Claude Opus 5
2026-08-12 02:46:41 -04:00
" (through the ProxyJump already in ~/.ssh/config) " : {
" fr " : " (par le ProxyJump déjà dans ~/.ssh/config) " ,
" en " : " (through the ProxyJump already in ~/.ssh/config) " ,
} ,
[ADD] script todo: lancer l'émulateur Android et son tunnel adb
Le tunnel n'était que du texte à recopier, et le démarrage restait manuel :
un second émulateur sur le même AVD tue le premier (« Running multiple
emulators »), rencontré deux fois. Le menu le détecte donc avant de lancer.
La fenêtre décide du reste. Sans elle, todo.py démarre à distance, détaché
par setsid dans le groupe kvm, et enchaîne sur scrcpy ; avec elle, la
commande doit partir du poste qui possède l'affichage, pas d'ici.
Mesuré : l'émulateur n'écoute que sur le 127.0.0.1 de la VM — l'hyperviseur
est refusé sur IP:5555 — d'où le -J qui met la VM en dernier saut. Vérifié
sur erplibre-mobile-proof : poignée de main adb CNXN rendant
sdk_gphone64_x86_64, enveloppe détachée survivant au ssh ; 30 tests.
--- EN ---
The tunnel was only text to copy, and starting the emulator stayed manual:
a second emulator on the same AVD kills the first ("Running multiple
emulators"), hit twice. The menu now detects it before starting.
The window decides the rest. Without one, todo.py starts it remotely,
detached by setsid in the kvm group, and chains into scrcpy; with one, the
command must run from the workstation that owns the display, not from here.
Measured: the emulator only listens on the VM's 127.0.0.1 — the hypervisor
is refused on IP:5555 — hence the -J putting the VM last. Verified on
erplibre-mobile-proof: an adb CNXN handshake returning sdk_gphone64_x86_64,
a detached wrapper outliving the ssh; 30 tests.
Assisted-by: Claude Opus 5
2026-08-19 03:35:31 -04:00
" (the hypervisor only relays; -J puts the VM last) " : {
" fr " : " (l ' hyperviseur ne fait que relayer ; -J met la VM en dernier) " ,
" en " : " (the hypervisor only relays; -J puts the VM last) " ,
} ,
[FIX] script todo: ne plus annoncer un émulateur qui n'a pas démarré
Trois défauts vus en conduisant le menu sur de vraies VM. « setsid » détache,
donc son code de retour vaut 0 même quand rien ne se lance : le menu disait
« Démarré » sur une VM sans SDK, dont le journal disait « not found ». Le
démarrage attend maintenant de VOIR le processus, et à défaut cite le journal.
Une sonde préalable lit binaire et AVD d'un coup : une VM déployée sans cocher
l'outil est le cas normal, pas une panne. Vérifié sur deux VM réelles — outillée
(True), migration (« aucun binaire emulator »), sans rien y démarrer.
Et tout ce qui n'était pas « 2 » démarrait l'émulateur : un « n » de travers
suffisait. Au passage, « Choice » n'était traduit dans aucun des trois menus
qui l'affichent.
--- EN ---
Three defects found while driving the menu against real VMs. setsid detaches,
so its exit code is 0 even when nothing launches: the menu said "Started" on a
VM with no SDK, whose log said "not found". Starting now waits to SEE the
process, and quotes the log when it never appears.
A prior probe reads binary and AVD in one go: a VM deployed without ticking the
tool is the normal case, not a failure. Verified on two real VMs — tooled
(True), migration ("no emulator binary") — without starting anything there.
And anything that was not "2" started the emulator; a stray "n" was enough.
Along the way, "Choice" was untranslated in all three menus showing it.
Assisted-by: Claude Opus 5
2026-08-19 03:46:12 -04:00
" Choice " : { " fr " : " Choix " , " en " : " Choice " } ,
[FIX] script todo: résumer les erreurs au lieu de chercher « error »
Le volet « d » et le compteur du tableau de bord cherchaient la sous-chaîne
« error ». Le journal de l'installation qui vient d'échouer — APK tué par le
noyau sur erplibre-ubuntu-2604-gnome — n'en contient AUCUNE : 0 ligne sur
8765, mesuré. Le volet annonçait « aucune erreur détectée » sur une machine
morte, et le tableau de bord 0 erreur.
Comptent désormais les marqueurs qui ne disent jamais « error » : « ⚠ ÉCHEC »,
« FAILURE », une trace Python, un « fatal: » de git, une mort par mémoire. Le
volet ouvre sur un résumé — l'étape en échec et son diagnostic, les signaux
durs, puis les répétitions comptées par forme. Sur ce journal : 1 étape nommée,
2 signaux, là où il n'affichait rien.
--- EN ---
The "d" pane and the dashboard counter looked for the "error" substring. The
log of the install that just failed — APK killed by the kernel on
erplibre-ubuntu-2604-gnome — contains NONE: 0 lines out of 8765, measured. The
pane said "no error detected" about a dead machine, and the dashboard 0 errors.
Markers that never say "error" now count: "⚠ ÉCHEC", "FAILURE", a Python
traceback, a git "fatal:", a death by memory. The pane opens on a summary — the
failed step with its diagnostic, the hard signals, then repeats counted by
shape. On that log: 1 named step and 2 signals, where it showed nothing.
Assisted-by: Claude Opus 5
2026-08-19 05:37:14 -04:00
" failed steps " : { " fr " : " étapes en échec " , " en " : " failed steps " } ,
" Failed steps " : { " fr " : " Étapes en échec " , " en " : " Failed steps " } ,
" Hard signals " : { " fr " : " Signaux durs " , " en " : " Hard signals " } ,
" Grouped by shape " : { " fr " : " Regroupé par forme " , " en " : " Grouped by shape " } ,
" line " : { " fr " : " ligne " , " en " : " line " } ,
[ADD] script todo: afficher la RAM de l'hôte dans le suivi d'installation
La barre montrait le CPU et le disque, jamais la mémoire. C'est pourtant la
seule des trois dont l'épuisement ne se voit nulle part ailleurs : une
compilation mobile s'est fait tuer par le noyau sur une VM de 12 Go sans swap
pendant que la barre affichait une charge tranquille et du disque de reste.
Lue dans /proc/meminfo, sans dépendance — ce suivi tourne sur l'hyperviseur,
donc sous Linux, d'où viennent déjà getloadavg et libvirt. « MemAvailable »
plutôt que « MemFree », presque nul dès que le cache travaille. Le swap
n'occupe la barre que s'il existe, et alors même à zéro : une machine qui
commence à échanger explique une lenteur. Au passage, « charge » et « libre »
s'affichaient en français dans une session anglaise.
--- EN ---
The bar showed CPU and disk, never memory. Yet memory is the one of the three
whose exhaustion shows up nowhere else: a mobile build was killed by the kernel
on a 12 GB VM with no swap while the bar displayed a quiet load and disk to
spare.
Read from /proc/meminfo, no dependency — this monitor runs on the hypervisor,
so on Linux, where getloadavg and libvirt already come from. "MemAvailable"
rather than "MemFree", near zero as soon as the cache is working. Swap takes
room in the bar only when it exists, and then even at zero: a machine starting
to swap explains a slowdown. Along the way, "load" and "free" were showing in
French in an English session.
Assisted-by: Claude Opus 5
2026-08-19 06:27:50 -04:00
" free space " : { " fr " : " libre " , " en " : " free " } ,
[ADD] script todo: dire depuis quand un journal d'installation est muet
Le sablier ne distingue pas une installation qui travaille d'une qui est morte :
le marqueur de sortie manque dans les deux cas. Une session ssh emportée, et le
tableau de bord a affiché « ⏳ » pendant 54 minutes sans que rien ne cloche à
l'œil.
La colonne d'état porte maintenant le silence du journal — « ⏳ silence 48min ».
C'est un chiffre, pas un verdict. Le seuil de dix minutes vient d'une mesure :
le téléchargement d'Android Studio tient ~5 min sans une ligne, et l'étape
« APK debug » davantage, son détail partant dans le journal de la VM. Plus bas,
chaque installation deviendrait une alerte, et l'alerte cesserait d'être lue.
--- EN ---
The hourglass does not tell a working install from a dead one: the exit marker
is missing in both cases. An ssh session was reaped, and the dashboard showed
"⏳" for 54 minutes with nothing looking wrong.
The state column now carries the log's silence — "⏳ silent 48min". It is a
figure, not a verdict. The ten-minute threshold comes from a measurement: the
Android Studio download holds ~5 min without a line, and the "debug APK" step
longer, its detail going to the VM's own log. Any lower and every install would
become an alert, and the alert would stop being read.
Assisted-by: Claude Opus 5
2026-08-19 07:26:54 -04:00
" silent " : { " fr " : " silence " , " en " : " silent " } ,
[FIX] script todo: nommer les outils qu'une VM sans ERPLibre ne peut pas poser
Les outils de la phase « après » vivent DANS le dépôt : la compilation mobile,
l'AVD, et maintenant le script Forgejo. Sur une VM « bureau seul », sans clone,
ils n'existent pas — et ils étaient écartés en silence. Une case cochée passait
donc pour honorée.
La commande le dit désormais, en nommant les outils concernés. Trois lignes qui
évitent de chercher pourquoi la forge demandée n'est nulle part.
--- EN ---
The "after" phase tools live IN the repository: the mobile build, the AVD, and
now the Forgejo script. On a desktop-only VM, with no clone, they do not exist —
and they were dropped in silence. A ticked checkbox therefore passed for
honoured.
The command now says so, naming the tools concerned. Three lines that save
hunting for why the requested forge is nowhere to be found.
Assisted-by: Claude Opus 5
2026-08-19 19:45:33 -04:00
" needs the ERPLibre install, skipped: " : {
" fr " : " exige l ' installation ERPLibre, écarté : " ,
" en " : " needs the ERPLibre install, skipped: " ,
} ,
[ADD] script forgejo: installer une forge git en option cochable
Une case au déploiement, et une forge git auto-hébergée répond sur le port 3000,
git par SSH sur 2222. Le travail vit dans un script dédié, appelable seul sur
une machine existante : une seule autorité pour les deux usages.
Le binaire officiel est statique, donc le même fichier sert apt, dnf, pacman et
zypper — c'est ce qui rend l'option portable sans une branche par distribution.
Les architectures suivent l'amont (amd64, arm64, arm-6) ; la case se grise sur
s390x plutôt que de poser un binaire inexécutable. Les quatre secrets sont
écrits par le script : sans oauth2.JWT_SECRET, Forgejo tente de les persister
lui-même et boucle sur un app.ini qu'il n'a pas le droit d'écrire.
Vérifié sur une VM : somme de contrôle validée, service actif, API qui répond,
dépôt créé puis cloné par git, et relance en 1,5 s sans rien réécrire.
--- EN ---
One checkbox at deploy time, and a self-hosted git forge answers on port 3000,
git over SSH on 2222. The work lives in a dedicated script, callable on its own
for an existing machine: one authority for both uses.
The official binary is static, so the same file serves apt, dnf, pacman and
zypper — that is what makes the option portable without a branch per
distribution. Architectures follow upstream (amd64, arm64, arm-6); the checkbox
greys out on s390x rather than dropping a binary that cannot run. The script
writes all four secrets itself: without oauth2.JWT_SECRET, Forgejo tries to
persist them and loops on an app.ini it is not allowed to write.
Verified on a VM: checksum validated, service active, API answering, a repo
created then cloned over git, and a replay in 1.5 s rewriting nothing.
Assisted-by: Claude Opus 5
2026-08-19 19:41:39 -04:00
" Forgejo (git forge) " : {
" fr " : " Forgejo (forge git) " ,
" en " : " Forgejo (git forge) " ,
} ,
" self-hosted git forge on :3000, SQLite " : {
" fr " : " forge git auto-hébergée sur :3000, SQLite " ,
" en " : " self-hosted git forge on :3000, SQLite " ,
} ,
[FIX] script todo: configurer le projet PyCharm avec le venv, et réessayer
Deux défauts empêchaient le .idea d'exister. La configuration d'abord :
« make pycharm_configure » lance le script avec le python SYSTÈME, qui n'a pas
xmltodict — « ModuleNotFoundError », mesuré. update_env_version.pycharm_update()
l'appelle depuis .venv.erplibre ; la cible make et l'étape font désormais pareil.
L'ouverture ensuite : la première tentative sur un dépôt neuf peut n'écrire
aucun .idea, son configurateur d'interpréteur plantant sur « homeDir is null »,
là où la suivante l'écrit en 25 s — constaté sur deux VM. L'étape retente donc
une fois, en gardant les deux journaux. Vérifié sur erplibre-ubuntu-2604-gnome,
caches effacés : erplibre.iml, misc.xml, modules.xml, vcs.xml, et 0 processus
survivant.
--- EN ---
Two defects kept .idea from existing. The configuration first: "make
pycharm_configure" runs the script with the SYSTEM python, which lacks
xmltodict — "ModuleNotFoundError", measured. update_env_version.pycharm_update()
calls it from .venv.erplibre; the make target and the step now do the same.
The open next: the first attempt on a fresh repo can write no .idea at all, its
interpreter configurator dying on "homeDir is null", where the next one writes
it in 25 s — seen on two VMs. The step therefore retries once, keeping both
logs. Verified on erplibre-ubuntu-2604-gnome with caches wiped: erplibre.iml,
misc.xml, modules.xml, vcs.xml, and 0 surviving processes.
Assisted-by: Claude Opus 5
2026-08-19 08:11:12 -04:00
" no project yet, second try on the warm caches " : {
" fr " : " pas de projet encore ; seconde tentative sur les caches chauds " ,
" en " : " no project yet, second try on the warm caches " ,
} ,
[IMP] script todo: ne pas retélécharger un IDE déjà installé
Rejouer une installation est le cas normal — une qui est morte, un outil ajouté
après coup — et le téléchargement en est la partie longue : environ cinq
minutes pour PyCharm, autant pour Android Studio, à chaque fois. Les deux
étapes vérifient donc /opt avant de sortir curl.
Mesuré sur erplibre-ubuntu-2604-gnome, IDE déjà posés : les deux étapes passent
de dix minutes à 0,094 s au total. Le reste rejoue quand même — lanceur, alias,
raccourci de bureau — il est idempotent et bon marché. Un test vérifie que la
garde n'avale pas l'échec du cas où il faut bel et bien télécharger.
--- EN ---
Replaying an install is the normal case — one that died, a tool added later —
and the download is the long part: about five minutes for PyCharm, as much for
Android Studio, every time. Both steps now check /opt before reaching for curl.
Measured on erplibre-ubuntu-2604-gnome with both IDEs already in place: the two
steps drop from ten minutes to 0.094 s total. The rest still replays — launcher,
alias, desktop entry — it is idempotent and cheap. A test checks the guard does
not swallow the failure of the case where downloading is actually needed.
Assisted-by: Claude Opus 5
2026-08-19 07:35:26 -04:00
" already there, download skipped " : {
" fr " : " déjà présent, téléchargement évité " ,
" en " : " already there, download skipped " ,
} ,
[FIX] script todo: ne plus laisser PyCharm manger la compilation mobile
Sur erplibre-ubuntu-2604-gnome, l'APK s'est fait tuer par le noyau. La cause
est ici : « $! » désigne xvfb-run, un script, et le tuer n'atteint ni PyCharm
ni Xvfb — l'IDE tournait encore 45 minutes après son étape, avec 1,9 Go, quand
Gradle a demandé ses 6,8 Go sur 12. C'est le GROUPE qu'on tue maintenant, et
plus rien ne survit : mesuré, 2 Go rendus.
Le .idea n'était jamais écrit non plus : 122 684 des 123 021 fichiers d'assets
du dépôt mobile épuisaient les 65 536 watches inotify. Relevées à 524288, le
projet se crée. Restent 4 Go de swap avant de compiler, et un diagnostic qui
nomme la mémoire — avec le compte de l'oom-killer — puis la limite ZIP de
65 535 entrées, sur laquelle la compilation bute désormais, en amont.
--- EN ---
On erplibre-ubuntu-2604-gnome the APK was killed by the kernel. The cause is
here: "$!" is xvfb-run, a script, and killing it reaches neither PyCharm nor
Xvfb — the IDE was still running 45 minutes after its step, holding 1.9 GB,
when Gradle asked for its 6.8 GB out of 12. The GROUP is killed now, and
nothing survives it: 2 GB given back, measured.
The .idea was never written either: 122,684 of the mobile repo's 123,021 asset
files exhausted the 65,536 inotify watches. Raised to 524288, the project gets
created. Also 4 GB of swap before building, and a diagnostic naming memory —
with the oom-killer count — then the 65,535-entry ZIP limit the build now hits,
upstream of us.
Assisted-by: Claude Opus 5
2026-08-19 05:38:01 -04:00
" inotify watches raised for the IDE " : {
" fr " : " watches inotify relevées pour l ' IDE " ,
" en " : " inotify watches raised for the IDE " ,
} ,
" closing what survived the first open: " : {
" fr " : " fermeture de ce qui a survécu à la première ouverture : " ,
" en " : " closing what survived the first open: " ,
} ,
" Gradle daemon killed: out of memory " : {
" fr " : " démon Gradle tué : mémoire épuisée " ,
" en " : " Gradle daemon killed: out of memory " ,
} ,
" out of memory " : { " fr " : " mémoire épuisée " , " en " : " out of memory " } ,
" Gradle heap too small " : {
" fr " : " tas Gradle trop petit " ,
" en " : " Gradle heap too small " ,
} ,
" too many asset files for one APK (ZIP limit: 65535 entries) " : {
" fr " : " trop de fichiers d ' assets pour un APK (limite ZIP : 65535 "
" entrées) " ,
" en " : " too many asset files for one APK (ZIP limit: 65535 entries) " ,
} ,
" 4 GB of swap added for the build " : {
" fr " : " 4 Go de swap ajoutés pour la compilation " ,
" en " : " 4 GB of swap added for the build " ,
} ,
" no swap could be added; build may run short " : {
" fr " : " swap impossible à ajouter ; la compilation peut manquer de mémoire " ,
" en " : " no swap could be added; build may run short " ,
} ,
" memory: " : { " fr " : " mémoire : " , " en " : " memory: " } ,
[FIX] script todo: ne plus empaqueter les dépôts du manifeste dans l'APK
La compilation butait sur « Too many zip entries 123678 (MAX=65535) » : un APK
est un ZIP, et le dépôt mobile verse 122 684 fichiers d'assets pour 337 qui
sont l'application. Le levier existe et il est documenté chez lui
(doc/SERVICES.md) : ERPLIBRE_MANIFEST_PATH, ici pointé sur un manifeste vide —
« ces dépôts-là : aucun ». Le plugin l'annonce, « 0 repos ».
Mesuré sur erplibre-ubuntu-2604-gnome : dist passe de 123 019 fichiers à 336,
l'APK sort à 59 Mo et 2 472 entrées, et la phase mobile entière rend 0, tests
Vitest compris — 75 fichiers, 1938 tests. Qui veut les dépôts pose la variable
lui-même : elle est respectée. Mesure d'attente, à retirer quand ils tiendront
sous le plafond du ZIP.
--- EN ---
The build hit "Too many zip entries 123678 (MAX=65535)": an APK is a ZIP, and
the mobile repo pours 122,684 asset files in for 337 that are the application.
The lever exists and that repo documents it (doc/SERVICES.md):
ERPLIBRE_MANIFEST_PATH, pointed here at an empty manifest — "those repos:
none". The plugin says so itself, "0 repos".
Measured on erplibre-ubuntu-2604-gnome: dist drops from 123,019 files to 336,
the APK comes out at 59 MB with 2,472 entries, and the whole mobile phase
returns 0, Vitest included — 75 files, 1938 tests. Set the variable yourself
and the repos come back. A stopgap, to drop once they fit under the ZIP
ceiling.
Assisted-by: Claude Opus 5
2026-08-19 05:57:22 -04:00
" manifest repos not bundled (ZIP entry limit) " : {
" fr " : " dépôts du manifeste non empaquetés (limite d ' entrées ZIP) " ,
" en " : " manifest repos not bundled (ZIP entry limit) " ,
} ,
[FIX] script todo: ne plus laisser PyCharm manger la compilation mobile
Sur erplibre-ubuntu-2604-gnome, l'APK s'est fait tuer par le noyau. La cause
est ici : « $! » désigne xvfb-run, un script, et le tuer n'atteint ni PyCharm
ni Xvfb — l'IDE tournait encore 45 minutes après son étape, avec 1,9 Go, quand
Gradle a demandé ses 6,8 Go sur 12. C'est le GROUPE qu'on tue maintenant, et
plus rien ne survit : mesuré, 2 Go rendus.
Le .idea n'était jamais écrit non plus : 122 684 des 123 021 fichiers d'assets
du dépôt mobile épuisaient les 65 536 watches inotify. Relevées à 524288, le
projet se crée. Restent 4 Go de swap avant de compiler, et un diagnostic qui
nomme la mémoire — avec le compte de l'oom-killer — puis la limite ZIP de
65 535 entrées, sur laquelle la compilation bute désormais, en amont.
--- EN ---
On erplibre-ubuntu-2604-gnome the APK was killed by the kernel. The cause is
here: "$!" is xvfb-run, a script, and killing it reaches neither PyCharm nor
Xvfb — the IDE was still running 45 minutes after its step, holding 1.9 GB,
when Gradle asked for its 6.8 GB out of 12. The GROUP is killed now, and
nothing survives it: 2 GB given back, measured.
The .idea was never written either: 122,684 of the mobile repo's 123,021 asset
files exhausted the 65,536 inotify watches. Raised to 524288, the project gets
created. Also 4 GB of swap before building, and a diagnostic naming memory —
with the oom-killer count — then the 65,535-entry ZIP limit the build now hits,
upstream of us.
Assisted-by: Claude Opus 5
2026-08-19 05:38:01 -04:00
" MB RAM, " : { " fr " : " Mo de RAM, " , " en " : " MB RAM, " } ,
" MB swap, kernel OOM kills: " : {
" fr " : " Mo de swap, tués par le noyau (OOM) : " ,
" en " : " MB swap, kernel OOM kills: " ,
} ,
[FIX] script todo: ne plus annoncer un émulateur qui n'a pas démarré
Trois défauts vus en conduisant le menu sur de vraies VM. « setsid » détache,
donc son code de retour vaut 0 même quand rien ne se lance : le menu disait
« Démarré » sur une VM sans SDK, dont le journal disait « not found ». Le
démarrage attend maintenant de VOIR le processus, et à défaut cite le journal.
Une sonde préalable lit binaire et AVD d'un coup : une VM déployée sans cocher
l'outil est le cas normal, pas une panne. Vérifié sur deux VM réelles — outillée
(True), migration (« aucun binaire emulator »), sans rien y démarrer.
Et tout ce qui n'était pas « 2 » démarrait l'émulateur : un « n » de travers
suffisait. Au passage, « Choice » n'était traduit dans aucun des trois menus
qui l'affichent.
--- EN ---
Three defects found while driving the menu against real VMs. setsid detaches,
so its exit code is 0 even when nothing launches: the menu said "Started" on a
VM with no SDK, whose log said "not found". Starting now waits to SEE the
process, and quotes the log when it never appears.
A prior probe reads binary and AVD in one go: a VM deployed without ticking the
tool is the normal case, not a failure. Verified on two real VMs — tooled
(True), migration ("no emulator binary") — without starting anything there.
And anything that was not "2" started the emulator; a stray "n" was enough.
Along the way, "Choice" was untranslated in all three menus showing it.
Assisted-by: Claude Opus 5
2026-08-19 03:46:12 -04:00
" Tick the Android emulator tool when deploying. " : {
" fr " : " Cochez l ' outil Émulateur Android au déploiement. " ,
" en " : " Tick the Android emulator tool when deploying. " ,
} ,
" Cannot reach this VM. " : {
" fr " : " VM injoignable. " ,
" en " : " Cannot reach this VM. " ,
} ,
" No Android SDK in this VM: no emulator binary. " : {
" fr " : " Pas de SDK Android dans cette VM : aucun binaire emulator. " ,
" en " : " No Android SDK in this VM: no emulator binary. " ,
} ,
" No AVD named erplibre in this VM. " : {
" fr " : " Aucun AVD nommé erplibre dans cette VM. " ,
" en " : " No AVD named erplibre in this VM. " ,
} ,
" It did not start; the VM log says: " : {
" fr " : " Il n ' a pas démarré ; le journal de la VM dit : " ,
" en " : " It did not start; the VM log says: " ,
} ,
[ADD] tui qemu: set every VM in place, type included
VM type was global: the whole fleet as servers, or all of them GNOME. It
now lives on the VM, all the way down -- the creation flag, and one remote
command per machine at install time, where a single one served them all.
The right pane is no longer a table but a row of widgets per VM: vCPU,
RAM, disk and type, each with its usual values and a free entry. The
left-hand fields become the shared default, which the screen now says. The
scope selector and the F2 modal go away: given two ways to do the same
thing, keep the visible one.
Three traps came out of it, and the tests lock them. Widget ids carry a
RANK, and the rank shifts when an entry is ticked, so an event from an
already destroyed widget applied to the VM that took its place -- rows now
carry a generation, marked BEFORE mounting, since mount_all empties the
pending children and marking after it was a race. The x1..x4 profile no
longer reached any VM. And a total of zero never said it had counted
nothing.
--- FR ---
Le type de VM était global : tout le parc en serveur, ou tout en GNOME. Il
vit désormais sur la VM, jusqu'au bout — le drapeau de création, et une
commande distante par machine à l'installation, là où une seule les
servait toutes.
Le panneau de droite n'est plus un tableau mais une rangée de widgets par
VM : vCPU, RAM, disque et type, chacun avec ses valeurs usuelles et une
saisie libre. Les champs de gauche deviennent le défaut commun, ce que
l'écran dit maintenant. Le sélecteur de portée et la modale F2 partent :
entre deux façons de faire la même chose, on garde la visible.
Trois pièges en sont sortis, et les tests les verrouillent. Les
identifiants de widgets portent un RANG, et le rang se décale quand on
coche une entrée : un événement émis par un widget déjà détruit
s'appliquait à la VM qui avait pris sa place — les rangées portent
maintenant une génération, marquée AVANT le montage, car mount_all vide
les enfants en attente et marquer après était une course. Le profil x1..x4
n'atteignait plus aucune VM. Et un total à zéro ne disait pas qu'il
n'avait rien compté.
Assisted-by: Claude Opus 5
2026-08-12 02:46:41 -04:00
" No ~/.ssh/config entry; see SSH configuration. " : {
" fr " : " Aucune entrée ~/.ssh/config ; voir Configuration SSH. " ,
" en " : " No ~/.ssh/config entry; see SSH configuration. " ,
} ,
" Not in an SSH session: check the host address. " : {
" fr " : " Hors session SSH : vérifiez l ' adresse de l ' hôte. " ,
" en " : " Not in an SSH session: check the host address. " ,
} ,
" The tunnel stays open as long as that ssh runs. " : {
" fr " : " Le tunnel reste ouvert tant que ce ssh tourne. " ,
" en " : " The tunnel stays open as long as that ssh runs. " ,
} ,
" No VM defined. " : { " fr " : " Aucune VM définie. " , " en " : " No VM defined. " } ,
" No host in ~/.ssh/config and no local VM. " : {
" fr " : " Aucun hôte dans ~/.ssh/config et aucune VM locale. " ,
" en " : " No host in ~/.ssh/config and no local VM. " ,
[ADD] tui qemu: customise one VM without touching the others
The three resource fields applied to the whole fleet. With a single
machine needing 16 G, the other eight got it too. A scope selector
settles it: all VMs, or the row targeted in the plan. The x1..x4 profile
stays global, multiplying what each image asks for; under "selected VM"
the fields are absolute, hence active even outside the custom profile.
The F2 modal already existed but was undiscoverable, and its cursor was
unusable: the table never held focus. It stays, the toggle now focuses
the table, F4 returns a VM to the shared profile, and the plan marks
customised rows with a ✎ -- without it, two rows with different
resources have no explanation on screen.
One defect found along the way: clear() reset the cursor to the top on
every redraw, and the plan recomputes on every keystroke. You picked
debian, typed the value, the table redrew, and the next entry landed on
ubuntu with nothing to show for it.
--- FR ---
Les trois champs de ressources s'appliquaient à tout le parc. Une seule
machine ayant besoin de 16 G, il fallait les donner aux huit autres. Un
sélecteur de portée tranche : toutes les VM, ou la ligne visée dans le
plan. Le profil x1..x4 reste global, il multiplie ce que demande chaque
image ; en portée « une seule » les champs valent en absolu, et sont donc
actifs même hors profil personnalisé.
La modale F2 existait déjà mais restait introuvable, et son curseur était
inutilisable : le tableau n'avait jamais le focus. Elle demeure, la
bascule lui donne le focus, F4 rend une VM au profil commun, et le plan
marque d'un ✎ ce qui a été personnalisé — sans marque, deux lignes aux
ressources différentes n'ont aucune explication à l'écran.
Un défaut au passage : « clear() » ramenait le curseur en tête à chaque
redessin, et le plan se recalcule à chaque frappe. On choisissait debian,
on tapait la valeur, le tableau se redessinait, et la saisie suivante
partait sur ubuntu sans que rien ne le montre.
Assisted-by: Claude Opus 5
2026-08-12 01:30:11 -04:00
} ,
[ADD] tui qemu: set every VM in place, type included
VM type was global: the whole fleet as servers, or all of them GNOME. It
now lives on the VM, all the way down -- the creation flag, and one remote
command per machine at install time, where a single one served them all.
The right pane is no longer a table but a row of widgets per VM: vCPU,
RAM, disk and type, each with its usual values and a free entry. The
left-hand fields become the shared default, which the screen now says. The
scope selector and the F2 modal go away: given two ways to do the same
thing, keep the visible one.
Three traps came out of it, and the tests lock them. Widget ids carry a
RANK, and the rank shifts when an entry is ticked, so an event from an
already destroyed widget applied to the VM that took its place -- rows now
carry a generation, marked BEFORE mounting, since mount_all empties the
pending children and marking after it was a race. The x1..x4 profile no
longer reached any VM. And a total of zero never said it had counted
nothing.
--- FR ---
Le type de VM était global : tout le parc en serveur, ou tout en GNOME. Il
vit désormais sur la VM, jusqu'au bout — le drapeau de création, et une
commande distante par machine à l'installation, là où une seule les
servait toutes.
Le panneau de droite n'est plus un tableau mais une rangée de widgets par
VM : vCPU, RAM, disque et type, chacun avec ses valeurs usuelles et une
saisie libre. Les champs de gauche deviennent le défaut commun, ce que
l'écran dit maintenant. Le sélecteur de portée et la modale F2 partent :
entre deux façons de faire la même chose, on garde la visible.
Trois pièges en sont sortis, et les tests les verrouillent. Les
identifiants de widgets portent un RANG, et le rang se décale quand on
coche une entrée : un événement émis par un widget déjà détruit
s'appliquait à la VM qui avait pris sa place — les rangées portent
maintenant une génération, marquée AVANT le montage, car mount_all vide
les enfants en attente et marquer après était une course. Le profil x1..x4
n'atteignait plus aucune VM. Et un total à zéro ne disait pas qu'il
n'avait rien compté.
Assisted-by: Claude Opus 5
2026-08-12 02:46:41 -04:00
" local VM " : { " fr " : " VM locale " , " en " : " local VM " } ,
" Remote desktop kind: " : {
" fr " : " Type de bureau distant : " ,
" en " : " Remote desktop kind: " ,
[ADD] tui qemu: customise one VM without touching the others
The three resource fields applied to the whole fleet. With a single
machine needing 16 G, the other eight got it too. A scope selector
settles it: all VMs, or the row targeted in the plan. The x1..x4 profile
stays global, multiplying what each image asks for; under "selected VM"
the fields are absolute, hence active even outside the custom profile.
The F2 modal already existed but was undiscoverable, and its cursor was
unusable: the table never held focus. It stays, the toggle now focuses
the table, F4 returns a VM to the shared profile, and the plan marks
customised rows with a ✎ -- without it, two rows with different
resources have no explanation on screen.
One defect found along the way: clear() reset the cursor to the top on
every redraw, and the plan recomputes on every keystroke. You picked
debian, typed the value, the table redrew, and the next entry landed on
ubuntu with nothing to show for it.
--- FR ---
Les trois champs de ressources s'appliquaient à tout le parc. Une seule
machine ayant besoin de 16 G, il fallait les donner aux huit autres. Un
sélecteur de portée tranche : toutes les VM, ou la ligne visée dans le
plan. Le profil x1..x4 reste global, il multiplie ce que demande chaque
image ; en portée « une seule » les champs valent en absolu, et sont donc
actifs même hors profil personnalisé.
La modale F2 existait déjà mais restait introuvable, et son curseur était
inutilisable : le tableau n'avait jamais le focus. Elle demeure, la
bascule lui donne le focus, F4 rend une VM au profil commun, et le plan
marque d'un ✎ ce qui a été personnalisé — sans marque, deux lignes aux
ressources différentes n'ont aucune explication à l'écran.
Un défaut au passage : « clear() » ramenait le curseur en tête à chaque
redessin, et le plan se recalcule à chaque frappe. On choisissait debian,
on tapait la valeur, le tableau se redessinait, et la saisie suivante
partait sur ubuntu sans que rien ne le montre.
Assisted-by: Claude Opus 5
2026-08-12 01:30:11 -04:00
} ,
[ADD] tui qemu: set every VM in place, type included
VM type was global: the whole fleet as servers, or all of them GNOME. It
now lives on the VM, all the way down -- the creation flag, and one remote
command per machine at install time, where a single one served them all.
The right pane is no longer a table but a row of widgets per VM: vCPU,
RAM, disk and type, each with its usual values and a free entry. The
left-hand fields become the shared default, which the screen now says. The
scope selector and the F2 modal go away: given two ways to do the same
thing, keep the visible one.
Three traps came out of it, and the tests lock them. Widget ids carry a
RANK, and the rank shifts when an entry is ticked, so an event from an
already destroyed widget applied to the VM that took its place -- rows now
carry a generation, marked BEFORE mounting, since mount_all empties the
pending children and marking after it was a race. The x1..x4 profile no
longer reached any VM. And a total of zero never said it had counted
nothing.
--- FR ---
Le type de VM était global : tout le parc en serveur, ou tout en GNOME. Il
vit désormais sur la VM, jusqu'au bout — le drapeau de création, et une
commande distante par machine à l'installation, là où une seule les
servait toutes.
Le panneau de droite n'est plus un tableau mais une rangée de widgets par
VM : vCPU, RAM, disque et type, chacun avec ses valeurs usuelles et une
saisie libre. Les champs de gauche deviennent le défaut commun, ce que
l'écran dit maintenant. Le sélecteur de portée et la modale F2 partent :
entre deux façons de faire la même chose, on garde la visible.
Trois pièges en sont sortis, et les tests les verrouillent. Les
identifiants de widgets portent un RANG, et le rang se décale quand on
coche une entrée : un événement émis par un widget déjà détruit
s'appliquait à la VM qui avait pris sa place — les rangées portent
maintenant une génération, marquée AVANT le montage, car mount_all vide
les enfants en attente et marquer après était une course. Le profil x1..x4
n'atteignait plus aucune VM. Et un total à zéro ne disait pas qu'il
n'avait rien compté.
Assisted-by: Claude Opus 5
2026-08-12 02:46:41 -04:00
" already defined, not counted " : {
" fr " : " déjà définie(s), non comptée(s) " ,
" en " : " already defined, not counted " ,
[ADD] tui qemu: customise one VM without touching the others
The three resource fields applied to the whole fleet. With a single
machine needing 16 G, the other eight got it too. A scope selector
settles it: all VMs, or the row targeted in the plan. The x1..x4 profile
stays global, multiplying what each image asks for; under "selected VM"
the fields are absolute, hence active even outside the custom profile.
The F2 modal already existed but was undiscoverable, and its cursor was
unusable: the table never held focus. It stays, the toggle now focuses
the table, F4 returns a VM to the shared profile, and the plan marks
customised rows with a ✎ -- without it, two rows with different
resources have no explanation on screen.
One defect found along the way: clear() reset the cursor to the top on
every redraw, and the plan recomputes on every keystroke. You picked
debian, typed the value, the table redrew, and the next entry landed on
ubuntu with nothing to show for it.
--- FR ---
Les trois champs de ressources s'appliquaient à tout le parc. Une seule
machine ayant besoin de 16 G, il fallait les donner aux huit autres. Un
sélecteur de portée tranche : toutes les VM, ou la ligne visée dans le
plan. Le profil x1..x4 reste global, il multiplie ce que demande chaque
image ; en portée « une seule » les champs valent en absolu, et sont donc
actifs même hors profil personnalisé.
La modale F2 existait déjà mais restait introuvable, et son curseur était
inutilisable : le tableau n'avait jamais le focus. Elle demeure, la
bascule lui donne le focus, F4 rend une VM au profil commun, et le plan
marque d'un ✎ ce qui a été personnalisé — sans marque, deux lignes aux
ressources différentes n'ont aucune explication à l'écran.
Un défaut au passage : « clear() » ramenait le curseur en tête à chaque
redessin, et le plan se recalcule à chaque frappe. On choisissait debian,
on tapait la valeur, le tableau se redessinait, et la saisie suivante
partait sur ubuntu sans que rien ne le montre.
Assisted-by: Claude Opus 5
2026-08-12 01:30:11 -04:00
} ,
[ADD] tui qemu: set every VM in place, type included
VM type was global: the whole fleet as servers, or all of them GNOME. It
now lives on the VM, all the way down -- the creation flag, and one remote
command per machine at install time, where a single one served them all.
The right pane is no longer a table but a row of widgets per VM: vCPU,
RAM, disk and type, each with its usual values and a free entry. The
left-hand fields become the shared default, which the screen now says. The
scope selector and the F2 modal go away: given two ways to do the same
thing, keep the visible one.
Three traps came out of it, and the tests lock them. Widget ids carry a
RANK, and the rank shifts when an entry is ticked, so an event from an
already destroyed widget applied to the VM that took its place -- rows now
carry a generation, marked BEFORE mounting, since mount_all empties the
pending children and marking after it was a race. The x1..x4 profile no
longer reached any VM. And a total of zero never said it had counted
nothing.
--- FR ---
Le type de VM était global : tout le parc en serveur, ou tout en GNOME. Il
vit désormais sur la VM, jusqu'au bout — le drapeau de création, et une
commande distante par machine à l'installation, là où une seule les
servait toutes.
Le panneau de droite n'est plus un tableau mais une rangée de widgets par
VM : vCPU, RAM, disque et type, chacun avec ses valeurs usuelles et une
saisie libre. Les champs de gauche deviennent le défaut commun, ce que
l'écran dit maintenant. Le sélecteur de portée et la modale F2 partent :
entre deux façons de faire la même chose, on garde la visible.
Trois pièges en sont sortis, et les tests les verrouillent. Les
identifiants de widgets portent un RANG, et le rang se décale quand on
coche une entrée : un événement émis par un widget déjà détruit
s'appliquait à la VM qui avait pris sa place — les rangées portent
maintenant une génération, marquée AVANT le montage, car mount_all vide
les enfants en attente et marquer après était une course. Le profil x1..x4
n'atteignait plus aucune VM. Et un total à zéro ne disait pas qu'il
n'avait rien compté.
Assisted-by: Claude Opus 5
2026-08-12 02:46:41 -04:00
" RAM: 2048 or 8G " : { " fr " : " RAM : 2048 ou 8G " , " en " : " RAM: 2048 or 8G " } ,
[ADD] tui qemu: F9 dumps the form state, widgets and model
A per-VM setting appears not to be taken into account, and nothing on
screen tells which link failed: the gap may be between the widget and
the model, or between the model and the spec. A screenshot shows
neither.
F9 writes both side by side into ~/.erplibre/deploy-form-dump.txt:
profile, shared values, overrides, locks, row generation, then VM by VM
what the list DISPLAYS against what the model HOLDS, and finally the
spec that would go to deployment.
The file can be pasted into a message, unlike an image, and it answers
the question on its own: if the list shows 16384 while the model says
1024, the defect is in the intake; if they agree and the created VM
differs, it is downstream, in deploy_qemu.
--- FR ---
Un réglage par VM ne semble pas pris en compte, et rien dans ce que
l'écran montre ne permet de trancher : l'écart peut être entre le widget
et le modèle, ou entre le modèle et la spec. Une capture d'écran ne dit
ni l'un ni l'autre.
F9 écrit les deux côte à côte dans ~/.erplibre/deploy-form-dump.txt :
profil, valeurs communes, surcharges, verrous, génération des rangées,
puis VM par VM ce que la liste AFFICHE contre ce que le modèle CONTIENT,
et enfin la spec qui partirait au déploiement.
Le fichier se recopie dans un message, contrairement à une image, et il
répond seul à la question : si la liste montre 16384 et que le modèle
dit 1024, le défaut est dans la prise en compte ; s'ils s'accordent et
que la VM créée diffère, il est en aval, dans deploy_qemu.
Assisted-by: Claude Opus 5
2026-08-13 04:51:27 -04:00
" Diagnostic dump " : { " fr " : " Diagnostic " , " en " : " Diagnostic dump " } ,
" State written to " : { " fr " : " État écrit dans " , " en " : " State written to " } ,
[ADD] tui qemu: set every VM in place, type included
VM type was global: the whole fleet as servers, or all of them GNOME. It
now lives on the VM, all the way down -- the creation flag, and one remote
command per machine at install time, where a single one served them all.
The right pane is no longer a table but a row of widgets per VM: vCPU,
RAM, disk and type, each with its usual values and a free entry. The
left-hand fields become the shared default, which the screen now says. The
scope selector and the F2 modal go away: given two ways to do the same
thing, keep the visible one.
Three traps came out of it, and the tests lock them. Widget ids carry a
RANK, and the rank shifts when an entry is ticked, so an event from an
already destroyed widget applied to the VM that took its place -- rows now
carry a generation, marked BEFORE mounting, since mount_all empties the
pending children and marking after it was a race. The x1..x4 profile no
longer reached any VM. And a total of zero never said it had counted
nothing.
--- FR ---
Le type de VM était global : tout le parc en serveur, ou tout en GNOME. Il
vit désormais sur la VM, jusqu'au bout — le drapeau de création, et une
commande distante par machine à l'installation, là où une seule les
servait toutes.
Le panneau de droite n'est plus un tableau mais une rangée de widgets par
VM : vCPU, RAM, disque et type, chacun avec ses valeurs usuelles et une
saisie libre. Les champs de gauche deviennent le défaut commun, ce que
l'écran dit maintenant. Le sélecteur de portée et la modale F2 partent :
entre deux façons de faire la même chose, on garde la visible.
Trois pièges en sont sortis, et les tests les verrouillent. Les
identifiants de widgets portent un RANG, et le rang se décale quand on
coche une entrée : un événement émis par un widget déjà détruit
s'appliquait à la VM qui avait pris sa place — les rangées portent
maintenant une génération, marquée AVANT le montage, car mount_all vide
les enfants en attente et marquer après était une course. Le profil x1..x4
n'atteignait plus aucune VM. Et un total à zéro ne disait pas qu'il
n'avait rien compté.
Assisted-by: Claude Opus 5
2026-08-12 02:46:41 -04:00
" Reset VM " : {
" fr " : " Réinit. VM " ,
" en " : " Reset VM " ,
[ADD] tui qemu: customise one VM without touching the others
The three resource fields applied to the whole fleet. With a single
machine needing 16 G, the other eight got it too. A scope selector
settles it: all VMs, or the row targeted in the plan. The x1..x4 profile
stays global, multiplying what each image asks for; under "selected VM"
the fields are absolute, hence active even outside the custom profile.
The F2 modal already existed but was undiscoverable, and its cursor was
unusable: the table never held focus. It stays, the toggle now focuses
the table, F4 returns a VM to the shared profile, and the plan marks
customised rows with a ✎ -- without it, two rows with different
resources have no explanation on screen.
One defect found along the way: clear() reset the cursor to the top on
every redraw, and the plan recomputes on every keystroke. You picked
debian, typed the value, the table redrew, and the next entry landed on
ubuntu with nothing to show for it.
--- FR ---
Les trois champs de ressources s'appliquaient à tout le parc. Une seule
machine ayant besoin de 16 G, il fallait les donner aux huit autres. Un
sélecteur de portée tranche : toutes les VM, ou la ligne visée dans le
plan. Le profil x1..x4 reste global, il multiplie ce que demande chaque
image ; en portée « une seule » les champs valent en absolu, et sont donc
actifs même hors profil personnalisé.
La modale F2 existait déjà mais restait introuvable, et son curseur était
inutilisable : le tableau n'avait jamais le focus. Elle demeure, la
bascule lui donne le focus, F4 rend une VM au profil commun, et le plan
marque d'un ✎ ce qui a été personnalisé — sans marque, deux lignes aux
ressources différentes n'ont aucune explication à l'écran.
Un défaut au passage : « clear() » ramenait le curseur en tête à chaque
redessin, et le plan se recalcule à chaque frappe. On choisissait debian,
on tapait la valeur, le tableau se redessinait, et la saisie suivante
partait sur ubuntu sans que rien ne le montre.
Assisted-by: Claude Opus 5
2026-08-12 01:30:11 -04:00
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" Preview " : {
" fr " : " Aperçu " ,
" en " : " Preview " ,
} ,
" All " : {
" fr " : " Tout " ,
" en " : " All " ,
} ,
" Main versions " : {
" fr " : " Versions principales " ,
" en " : " Main versions " ,
} ,
" None " : {
" fr " : " Rien " ,
" en " : " None " ,
} ,
" Cancel " : {
" fr " : " Annuler " ,
" en " : " Cancel " ,
} ,
" Apply " : {
" fr " : " Appliquer " ,
" en " : " Apply " ,
} ,
" Close " : {
" fr " : " Fermer " ,
" en " : " Close " ,
} ,
" Copy log " : {
" fr " : " Copier le log " ,
" en " : " Copy log " ,
} ,
" Copy all logs " : {
" fr " : " Copier tous les logs " ,
" en " : " Copy all logs " ,
} ,
" all logs " : {
" fr " : " tous les logs " ,
" en " : " all logs " ,
} ,
" c copy log · C copy all · q quit " : {
" fr " : " c copier le log · C copier tout · q quitter " ,
" en " : " c copy log · C copy all · q quit " ,
} ,
" Clipboard " : {
" fr " : " Presse-papiers " ,
" en " : " Clipboard " ,
} ,
" chars " : {
" fr " : " car. " ,
" en " : " chars " ,
} ,
" tail only, log was truncated " : {
" fr " : " fin seulement, log tronqué " ,
" en " : " tail only, log was truncated " ,
} ,
" Needs an OSC 52 capable terminal. " : {
" fr " : " Nécessite un terminal compatible OSC 52. " ,
" en " : " Needs an OSC 52 capable terminal. " ,
} ,
" Nothing to copy. " : {
" fr " : " Rien à copier. " ,
" en " : " Nothing to copy. " ,
} ,
2026-08-07 03:22:26 -04:00
" TODO navigation telemetry " : {
" fr " : " TODO — télémétrie de navigation " ,
" en " : " TODO navigation telemetry " ,
} ,
" navigations " : {
" fr " : " navigations " ,
" en " : " navigations " ,
} ,
" menus " : {
" fr " : " menus " ,
" en " : " menus " ,
} ,
" Telemetry reset. " : {
" fr " : " Télémétrie réinitialisée. " ,
" en " : " Telemetry reset. " ,
} ,
" tree from code " : {
" fr " : " arbre issu du code " ,
" en " : " tree from code " ,
} ,
" visited paths only " : {
" fr " : " chemins visités seulement " ,
" en " : " visited paths only " ,
} ,
" Enter = run, F3 = view " : {
" fr " : " Entrée = exécuter, F3 = vue " ,
" en " : " Enter = run, F3 = view " ,
} ,
" Enter = run, F3/F4 = views " : {
" fr " : " Entrée = exécuter, F3/F4 = vues " ,
" en " : " Enter = run, F3/F4 = views " ,
} ,
" view " : {
" fr " : " vue " ,
" en " : " view " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" avg " : {
" fr " : " moy " ,
" en " : " avg " ,
} ,
" Last install: " : {
" fr " : " Dernière install : " ,
" en " : " Last install: " ,
} ,
2026-08-07 03:22:26 -04:00
" No command found. " : {
" fr " : " Aucune commande trouvée. " ,
" en " : " No command found. " ,
} ,
" Back to telemetry (r) or quit (Enter)? " : {
" fr " : " Revenir à la télémétrie (r) ou quitter (Entrée) ? " ,
" en " : " Back to telemetry (r) or quit (Enter)? " ,
} ,
" F2 system · F3/F4 views · Enter run " : {
" fr " : " F2 système · F3/F4 vues · Entrée exécute " ,
" en " : " F2 system · F3/F4 views · Enter run " ,
} ,
" State " : { " fr " : " État " , " en " : " State " } ,
" uptime " : { " fr " : " actif depuis " , " en " : " uptime " } ,
" load " : { " fr " : " charge " , " en " : " load " } ,
" cores " : { " fr " : " cœurs " , " en " : " cores " } ,
" Memory " : { " fr " : " Mémoire " , " en " : " Memory " } ,
" Disk " : { " fr " : " Disque " , " en " : " Disk " } ,
" Network " : { " fr " : " Réseau " , " en " : " Network " } ,
" Battery " : { " fr " : " Batterie " , " en " : " Battery " } ,
" Temperature " : { " fr " : " Température " , " en " : " Temperature " } ,
" lm-sensors absent — press i to install " : {
" fr " : " lm-sensors absent — appuyez sur i pour installer " ,
" en " : " lm-sensors absent — press i to install " ,
} ,
" Sensors already available. " : {
" fr " : " Capteurs déjà disponibles. " ,
" en " : " Sensors already available. " ,
} ,
" Sensors now available. " : {
" fr " : " Capteurs désormais disponibles. " ,
" en " : " Sensors now available. " ,
} ,
" Still no temperature (reboot/modprobe may be needed). " : {
" fr " : " Toujours pas de température (redémarrage/modprobe requis ?). " ,
" en " : " Still no temperature (reboot/modprobe may be needed). " ,
} ,
" Unknown package manager for lm-sensors. " : {
" fr " : " Gestionnaire de paquets inconnu pour lm-sensors. " ,
" en " : " Unknown package manager for lm-sensors. " ,
} ,
" Proposed install command: " : {
" fr " : " Commande d ' installation proposée : " ,
" en " : " Proposed install command: " ,
} ,
" Install lm-sensors now? (y/N): " : {
" fr " : " Installer lm-sensors maintenant ? (o/N) : " ,
" en " : " Install lm-sensors now? (y/N): " ,
} ,
" Command failed: " : {
" fr " : " Échec de la commande : " ,
" en " : " Command failed: " ,
} ,
[ADD] migration: see and repair the website COW views
A copy-on-write view freezes the module view it came from; the module moves
on and the upgrade dies hours later on a missing anchor. These tools
predict, snapshot, diff, neutralize and reset them. The migration screen
gains the real state of each step, replay from any of them, and statistics.
--- FR ---
Une vue copy-on-write fige la vue de module dont elle vient ; le module
évolue et la mise à niveau meurt des heures plus tard sur un point
d'ancrage absent. Ces outils les prévoient, photographient, comparent,
neutralisent et réinitialisent. L'écran de migration gagne l'état réel de
chaque étape, la reprise depuis n'importe laquelle, et des statistiques.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
" Textual is required for this screen. " : {
" fr " : " Textual est nécessaire pour cet écran. " ,
" en " : " Textual is required for this screen. " ,
} ,
" Install it now? (Y/n): " : {
" fr " : " L ' installer maintenant ? (O/n, défaut : oui) : " ,
" en " : " Install it now? (Y/n, default: yes): " ,
} ,
" Textual is installed. " : {
" fr " : " Textual est installé. " ,
" en " : " Textual is installed. " ,
} ,
" Installation finished but textual is still missing. " : {
" fr " : " Installation terminée mais textual reste introuvable. " ,
" en " : " Installation finished but textual is still missing. " ,
} ,
" pip exited with " : {
" fr " : " pip a retourné " ,
" en " : " pip exited with " ,
} ,
2026-08-07 03:22:26 -04:00
" Your distribution may package it as python3-textual. " : {
" fr " : " Votre distribution le fournit peut-être en python3-textual. " ,
" en " : " Your distribution may package it as python3-textual. " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" Resize a VM disk " : {
" fr " : " 📐 Redimensionner le disque d ' une VM " ,
" en " : " 📐 Resize a VM disk " ,
} ,
" VM name to resize: " : {
" fr " : " Nom de la VM à redimensionner : " ,
" en " : " VM name to resize: " ,
} ,
" VM not found. " : {
" fr " : " VM introuvable. " ,
" en " : " VM not found. " ,
} ,
" Main disk not found for this VM. " : {
" fr " : " Disque principal introuvable pour cette VM. " ,
" en " : " Main disk not found for this VM. " ,
} ,
" Current disk: " : {
" fr " : " Disque actuel : " ,
" en " : " Current disk: " ,
} ,
" Current virtual size: " : {
" fr " : " Taille virtuelle actuelle : " ,
" en " : " Current virtual size: " ,
} ,
" VM state: " : {
" fr " : " État de la VM : " ,
" en " : " VM state: " ,
} ,
" Could not read current disk size; aborting. " : {
" fr " : " Impossible de lire la taille actuelle du disque ; abandon. " ,
" en " : " Could not read current disk size; aborting. " ,
} ,
" Host free space: " : {
" fr " : " Espace libre hôte : " ,
" en " : " Host free space: " ,
} ,
" max sustainable total (before host full): " : {
" fr " : " max soutenable au total (avant hôte plein) : " ,
" en " : " max sustainable total (before host full): " ,
} ,
" Beyond host capacity by ~ %.1f G — overcommit. " : {
" fr " : " Dépasse la capacité de l ' hôte de ~ %.1f G — surallocation. " ,
" en " : " Beyond host capacity by ~ %.1f G — overcommit. " ,
} ,
" The qcow2 is thin: fine until the VM fills it, then the "
" host disk runs out. Max sustainable: ~ %.1f G. " : {
" fr " : " Le qcow2 est creux : OK tant que la VM ne remplit pas, sinon "
" l ' hôte tombe à court. Max soutenable : ~ %.1f G. " ,
" en " : " The qcow2 is thin: fine until the VM fills it, then the "
" host disk runs out. Max sustainable: ~ %.1f G. " ,
} ,
" Safe shrink needs libguestfs (virt-resize). " : {
" fr " : " La réduction sûre nécessite libguestfs (virt-resize). " ,
" en " : " Safe shrink needs libguestfs (virt-resize). " ,
} ,
" Install libguestfs-tools now? (y/N): " : {
" fr " : " Installer libguestfs-tools maintenant ? (o/N) : " ,
" en " : " Install libguestfs-tools now? (y/N): " ,
} ,
" Shrink aborted; disk left intact. " : {
" fr " : " Réduction annulée ; disque laissé intact. " ,
" en " : " Shrink aborted; disk left intact. " ,
} ,
" Could not detect the partition to shrink; aborting. " : {
" fr " : " Partition à réduire introuvable ; abandon. " ,
" en " : " Could not detect the partition to shrink; aborting. " ,
} ,
" Missing tools for safe shrink: " : {
" fr " : " Outils manquants pour la réduction sûre : " ,
" en " : " Missing tools for safe shrink: " ,
} ,
" Backing up the disk before shrinking… " : {
" fr " : " Sauvegarde du disque avant réduction… " ,
" en " : " Backing up the disk before shrinking… " ,
} ,
" Back up the disk before shrinking? (Y/n): " : {
" fr " : " Sauvegarder le disque avant réduction ? (O/n, défaut : oui) : " ,
" en " : " Back up the disk before shrinking? (Y/n, default: yes): " ,
} ,
" No backup: a failure could leave the disk broken. " : {
" fr " : " Sans sauvegarde : un échec pourrait rendre le disque inutilisable. " ,
" en " : " No backup: a failure could leave the disk broken. " ,
} ,
" Disk safely shrunk. " : {
" fr " : " Disque réduit en toute sécurité. " ,
" en " : " Disk safely shrunk. " ,
} ,
" No backup to restore; run fsck on the disk before use. " : {
" fr " : " Pas de sauvegarde à restaurer ; lancez fsck avant utilisation. " ,
" en " : " No backup to restore; run fsck on the disk before use. " ,
} ,
" A disk backup was kept: " : {
" fr " : " Une sauvegarde du disque a été conservée : " ,
" en " : " A disk backup was kept: " ,
} ,
" Delete this backup now? (y/N): " : {
" fr " : " Effacer cette sauvegarde maintenant ? (o/N) : " ,
" en " : " Delete this backup now? (y/N): " ,
} ,
" Backup deleted. " : {
" fr " : " Sauvegarde effacée. " ,
" en " : " Backup deleted. " ,
} ,
" Backup kept (delete later via Clean up QEMU). " : {
" fr " : " Sauvegarde conservée (à effacer plus tard via Nettoyer QEMU). " ,
" en " : " Backup kept (delete later via Clean up QEMU). " ,
} ,
" disk backup (resize) " : {
" fr " : " sauvegarde de disque (redim.) " ,
" en " : " disk backup (resize) " ,
} ,
" Backup failed; aborting. " : {
" fr " : " Échec de la sauvegarde ; abandon. " ,
" en " : " Backup failed; aborting. " ,
} ,
" Could not attach the disk (nbd); aborting. " : {
" fr " : " Impossible d ' attacher le disque (nbd) ; abandon. " ,
" en " : " Could not attach the disk (nbd); aborting. " ,
} ,
" Only ext2/3/4 can be shrunk safely; aborting. " : {
" fr " : " Seul ext2/3/4 peut être réduit en sécurité ; abandon. " ,
" en " : " Only ext2/3/4 can be shrunk safely; aborting. " ,
} ,
" Target size too small for this layout; aborting. " : {
" fr " : " Taille cible trop petite pour cette disposition ; abandon. " ,
" en " : " Target size too small for this layout; aborting. " ,
} ,
" Not enough used-space margin to shrink; aborting. " : {
" fr " : " Espace utilisé trop proche de la cible ; abandon. " ,
" en " : " Not enough used-space margin to shrink; aborting. " ,
} ,
" Shrinking guest ext filesystem " : {
" fr " : " Réduction du système de fichiers ext invité " ,
" en " : " Shrinking guest ext filesystem " ,
} ,
" resize2fs failed; reverting. " : {
" fr " : " resize2fs a échoué ; restauration. " ,
" en " : " resize2fs failed; reverting. " ,
} ,
" Internal size check failed; reverting. " : {
" fr " : " Contrôle de taille interne échoué ; restauration. " ,
" en " : " Internal size check failed; reverting. " ,
} ,
" Shrinking the partition… " : {
" fr " : " Réduction de la partition… " ,
" en " : " Shrinking the partition… " ,
} ,
" Partition rewrite failed; reverting. " : {
" fr " : " Réécriture de la partition échouée ; restauration. " ,
" en " : " Partition rewrite failed; reverting. " ,
} ,
" Shrinking the qcow2 container… " : {
" fr " : " Réduction du conteneur qcow2… " ,
" en " : " Shrinking the qcow2 container… " ,
} ,
" Container shrink failed; reverting. " : {
" fr " : " Réduction du conteneur échouée ; restauration. " ,
" en " : " Container shrink failed; reverting. " ,
} ,
" Restoring the original disk from backup… " : {
" fr " : " Restauration du disque d ' origine depuis la sauvegarde… " ,
" en " : " Restoring the original disk from backup… " ,
} ,
" Shrinking guest FS + partition via virt-resize " : {
" fr " : " Réduction du FS invité + partition via virt-resize " ,
" en " : " Shrinking guest FS + partition via virt-resize " ,
} ,
" virt-resize failed; original disk left intact. " : {
" fr " : " virt-resize a échoué ; disque d ' origine laissé intact. " ,
" en " : " virt-resize failed; original disk left intact. " ,
} ,
" Disk safely shrunk. Backup kept at: " : {
" fr " : " Disque réduit en toute sécurité. Sauvegarde conservée : " ,
" en " : " Disk safely shrunk. Backup kept at: " ,
} ,
" click to expand " : {
" fr " : " cliquer pour déplier " ,
" en " : " click to expand " ,
} ,
" No error detected. " : {
" fr " : " Aucune erreur détectée. " ,
" en " : " No error detected. " ,
} ,
" Test a VM (open Odoo in a CLI browser) " : {
" fr " : " 🧪 Tester une VM (ouvrir Odoo dans un navigateur CLI) " ,
" en " : " 🧪 Test a VM (open Odoo in a CLI browser) " ,
} ,
" Reopen install monitoring (last run / history) " : {
" fr " : " 📈 Rouvrir le suivi d ' installation (dernier run / historique) " ,
" en " : " 📈 Reopen install monitoring (last run / history) " ,
} ,
" No install run found in history. " : {
" fr " : " Aucun run d ' installation dans l ' historique. " ,
" en " : " No install run found in history. " ,
} ,
" Install runs (most recent first): " : {
" fr " : " Runs d ' installation (du plus récent) : " ,
" en " : " Install runs (most recent first): " ,
} ,
" Choice (number, blank = last): " : {
" fr " : " Choix (numéro, vide = le dernier) : " ,
" en " : " Choice (number, blank = last): " ,
} ,
" Invalid selection. " : {
" fr " : " Sélection invalide. " ,
" en " : " Invalid selection. " ,
} ,
" Resolving VM IP... " : {
" fr " : " Résolution de l ' IP de la VM… " ,
" en " : " Resolving VM IP... " ,
} ,
" No IP found for this VM. " : {
" fr " : " Aucune IP trouvée pour cette VM. " ,
" en " : " No IP found for this VM. " ,
} ,
" No CLI browser installed. Which to install? " : {
" fr " : " Aucun navigateur CLI installé. Lequel installer ? " ,
" en " : " No CLI browser installed. Which to install? " ,
} ,
" Which browser to install? " : {
" fr " : " Quel navigateur installer ? " ,
" en " : " Which browser to install? " ,
} ,
" Install another browser " : {
" fr " : " Installer un autre navigateur " ,
" en " : " Install another browser " ,
} ,
" Choice (number, blank = w3m): " : {
" fr " : " Choix (numéro, vide = w3m) : " ,
" en " : " Choice (number, blank = w3m): " ,
} ,
" Unknown package manager; install it manually. " : {
" fr " : " Gestionnaire de paquets inconnu ; installez-le manuellement. " ,
" en " : " Unknown package manager; install it manually. " ,
} ,
" Install now? (y/N): " : {
" fr " : " Installer maintenant ? (o/N) : " ,
" en " : " Install now? (y/N): " ,
} ,
" Which browser to view the page? " : {
" fr " : " Quel navigateur pour voir la page ? " ,
" en " : " Which browser to view the page? " ,
} ,
" Choice (number, blank = first): " : {
" fr " : " Choix (numéro, vide = le premier) : " ,
" en " : " Choice (number, blank = first): " ,
} ,
" Page may not have loaded: Odoo not started on :8069, "
" or network/firewall. " : {
" fr " : " La page ne s ' est peut-être pas affichée : Odoo n ' est pas "
" démarré sur :8069, ou réseau/pare-feu. " ,
" en " : " Page may not have loaded: Odoo not started on :8069, "
" or network/firewall. " ,
} ,
" errors " : {
" fr " : " erreurs " ,
" en " : " errors " ,
} ,
" warnings " : {
" fr " : " avertissements " ,
" en " : " warnings " ,
} ,
" Esc to close " : {
" fr " : " Échap pour fermer " ,
" en " : " Esc to close " ,
} ,
" No running VM to pause. " : {
" fr " : " Aucune VM en cours à mettre en pause. " ,
" en " : " No running VM to pause. " ,
} ,
" No paused VM to resume. " : {
" fr " : " Aucune VM en pause à reprendre. " ,
" en " : " No paused VM to resume. " ,
} ,
" resumed " : {
" fr " : " reprise(s) " ,
" en " : " resumed " ,
} ,
" SSH grow failed; trying the guest agent. " : {
" fr " : " Échec SSH ; tentative via l ' agent invité. " ,
" en " : " SSH grow failed; trying the guest agent. " ,
} ,
" No IP; trying the guest agent (no network). " : {
" fr " : " Pas d ' IP ; tentative via l ' agent invité (sans réseau). " ,
" en " : " No IP; trying the guest agent (no network). " ,
} ,
" Guest filesystem grown via guest agent. " : {
" fr " : " FS invité étendu via l ' agent invité. " ,
" en " : " Guest filesystem grown via guest agent. " ,
} ,
" Guest agent grow failed; falling back to console. " : {
" fr " : " Échec de l ' agent invité ; repli sur la console. " ,
" en " : " Guest agent grow failed; falling back to console. " ,
} ,
" Guest agent unavailable; falling back to serial console. " : {
" fr " : " Agent invité indisponible ; repli sur la console série. " ,
" en " : " Guest agent unavailable; falling back to serial console. " ,
} ,
" Running via guest agent (no network)… " : {
" fr " : " Exécution via l ' agent invité (sans réseau)… " ,
" en " : " Running via guest agent (no network)… " ,
} ,
" SSH grow failed; falling back to serial console. " : {
" fr " : " Échec de l ' extension via SSH ; repli sur la console série. " ,
" en " : " SSH grow failed; falling back to serial console. " ,
} ,
" No IP; falling back to serial console. " : {
" fr " : " Pas d ' IP ; repli sur la console série. " ,
" en " : " No IP; falling back to serial console. " ,
} ,
" Serial console fallback. Log in, then paste: " : {
" fr " : " Repli console série. Connectez-vous, puis collez : " ,
" en " : " Serial console fallback. Log in, then paste: " ,
} ,
" Open the serial console now? (y/N): " : {
" fr " : " Ouvrir la console série maintenant ? (o/N) : " ,
" en " : " Open the serial console now? (y/N): " ,
} ,
" The VM must be off. Shut it down and retry? (y/N): " : {
" fr " : " La VM doit être éteinte. L ' éteindre et réessayer ? (o/N) : " ,
" en " : " The VM must be off. Shut it down and retry? (y/N): " ,
} ,
" VM is still not off; aborting. " : {
" fr " : " La VM n ' est toujours pas éteinte ; abandon. " ,
" en " : " VM is still not off; aborting. " ,
} ,
" Waiting for the VM to shut down... " : {
" fr " : " Attente de l ' arrêt de la VM… " ,
" en " : " Waiting for the VM to shut down... " ,
} ,
2026-08-07 03:22:26 -04:00
" timeout " : {
" fr " : " délai max " ,
" en " : " timeout " ,
} ,
" shutting down " : {
" fr " : " arrêt en cours " ,
" en " : " shutting down " ,
} ,
" remaining " : {
" fr " : " restantes " ,
" en " : " remaining " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" VM is off. " : {
" fr " : " VM éteinte. " ,
" en " : " VM is off. " ,
} ,
" The VM was shut down for the resize. " : {
" fr " : " La VM a été éteinte pour le redimensionnement. " ,
" en " : " The VM was shut down for the resize. " ,
} ,
" Start the VM now? (y/N): " : {
" fr " : " Démarrer la VM maintenant ? (o/N) : " ,
" en " : " Start the VM now? (y/N): " ,
} ,
" Graceful shutdown timed out. Force off (destroy)? (y/N): " : {
" fr " : " Arrêt gracieux trop long. Forcer l ' arrêt (destroy) ? (o/N) : " ,
" en " : " Graceful shutdown timed out. Force off (destroy)? (y/N): " ,
} ,
" Show advanced info (vCPU, RAM, disk)? (y/N): " : {
" fr " : " Afficher les infos avancées (vCPU, RAM, disque) ? (o/N) : " ,
" en " : " Show advanced info (vCPU, RAM, disk)? (y/N): " ,
} ,
" What do you want to do? " : {
" fr " : " Que voulez-vous faire ? " ,
" en " : " What do you want to do? " ,
} ,
" Advanced info (vCPU, RAM, disk) " : {
" fr " : " Infos avancées (vCPU, RAM, disque) " ,
" en " : " Advanced info (vCPU, RAM, disk) " ,
} ,
" Change the state of one or more VMs " : {
" fr " : " Changer l ' état d ' une ou plusieurs VM " ,
" en " : " Change the state of one or more VMs " ,
} ,
" Enter " : {
" fr " : " Entrée " ,
" en " : " Enter " ,
} ,
" Nothing " : {
" fr " : " Rien " ,
" en " : " Nothing " ,
} ,
" Choice: " : {
" fr " : " Choix : " ,
" en " : " Choice: " ,
} ,
" Available VMs: " : {
" fr " : " VM disponibles : " ,
" en " : " Available VMs: " ,
} ,
" VMs to change (comma-separated): " : {
" fr " : " VM à modifier (séparées par des virgules) : " ,
" en " : " VMs to change (comma-separated): " ,
} ,
[ADD] qemu : prendre le GPU de l'hôte, et régler le matériel des VM
Une VM graphique sans accélération rend tout par le processeur : le bureau,
et l'émulateur Android qui tourne dedans — 32 % d'images en retard, mesuré.
Le déploiement prend donc le GPU de l'hôte dès qu'un nœud de rendu existe.
Une VM déjà installée n'avait aucune voie : ni vCPU, ni RAM, ni 3D. Le menu
d'état les règle maintenant, pendant qu'elle est éteinte — le seul moment où
libvirt les lit.
Trois pièges du terrain : « --memory N » ne touche que le ballon, l'ajout
d'egl-headless n'est pas idempotent, et son retrait sans cible emporte la
console VNC. Vérifié sur un domaine jetable, 459 tests verts.
--- EN ---
A graphical VM without acceleration renders everything on the CPU: the
desktop, and the Android emulator inside it — 32 % janky frames, measured.
The deployment now takes the host GPU as soon as a render node exists.
An installed VM had no path at all: no vCPU, no RAM, no 3D. The state menu
now sets them while the VM is shut off — the only moment libvirt reads them.
Three field traps: "--memory N" only moves the balloon, adding egl-headless
is not idempotent, and removing it untargeted takes the VNC console with it.
Verified on a throwaway domain, 459 tests green.
Assisted-by: Claude Opus 5
2026-08-21 22:32:56 -04:00
" Adjust hardware only (vCPU, RAM, 3D) " : {
" fr " : " Régler le matériel seulement (vCPU, RAM, 3D) " ,
" en " : " Adjust hardware only (vCPU, RAM, 3D) " ,
} ,
" Adjust hardware before starting? (y/N): " : {
" fr " : " Régler le matériel avant de démarrer ? (o/N) : " ,
" en " : " Adjust hardware before starting? (y/N): " ,
} ,
" Not shut off, hardware left untouched: " : {
" fr " : " Pas éteintes, matériel inchangé : " ,
" en " : " Not shut off, hardware left untouched: " ,
} ,
" Host GPU: " : {
" fr " : " GPU de l ' hôte : " ,
" en " : " Host GPU: " ,
} ,
" none (software rendering) " : {
" fr " : " aucun (rendu logiciel) " ,
" en " : " none (software rendering) " ,
} ,
" software rendering " : {
" fr " : " rendu logiciel " ,
" en " : " software rendering " ,
} ,
" Unreadable VM definition. " : {
" fr " : " Définition de VM illisible. " ,
" en " : " Unreadable VM definition. " ,
} ,
" Nothing to change. " : {
" fr " : " Rien à changer. " ,
" en " : " Nothing to change. " ,
} ,
" Changes: " : {
" fr " : " Changements : " ,
" en " : " Changes: " ,
} ,
" Apply these changes? (y/N): " : {
" fr " : " Appliquer ces changements ? (o/N) : " ,
" en " : " Apply these changes? (y/N): " ,
} ,
" Autostart " : {
" fr " : " Démarrage automatique " ,
" en " : " Autostart " ,
} ,
" VM hardware " : {
" fr " : " Matériel des VM " ,
" en " : " VM hardware " ,
} ,
" 3D " : {
" fr " : " 3D " ,
" en " : " 3D " ,
} ,
" 3D acceleration (host GPU) " : {
" fr " : " Accélération 3D (GPU de l ' hôte) " ,
" en " : " 3D acceleration (host GPU) " ,
} ,
" Render node " : {
" fr " : " Nœud de rendu " ,
" en " : " Render node " ,
} ,
[FIX] déploiement : suivre une VM même sans installation ERPLibre
Décocher l'installation d'ERPLibre faisait disparaître le tableau de bord. La
case « suivi » vivait DANS le groupe de l'installation, build_spec ne la
recopiait même pas dans la spec, et l'épilogue était gardé par « if install or
desktop » : sans rien à installer, il ne se passait rien.
Le suivi devient un choix du DÉPLOIEMENT. Et sans rien à installer, la
commande distante ne vaut plus « true » — journal vide, ✅ instantané : elle
regarde la VM ARRIVER, attend cloud-init, puis relève système, noyau, adresse,
disque et mémoire. Le journal cesse aussi d'annoncer une installation ERPLibre
qui n'a pas lieu.
--- EN ---
Unchecking the ERPLibre install made the dashboard vanish. The "monitoring"
checkbox lived INSIDE the install group, build_spec did not even copy it into
the spec, and the deploy epilogue was gated by "if install or desktop": with
nothing to install, nothing happened.
Monitoring is now a DEPLOYMENT-level choice. And with nothing to install, the
remote command is no longer "true" — empty log, instant ✅: it watches the VM
ARRIVE, waits for cloud-init, then reports system, kernel, address, disk and
memory. The log also stops announcing an ERPLibre install that never happens.
Assisted-by: Claude Opus 5
2026-08-23 03:50:57 -04:00
" VM ready - taking its measurements " : {
" fr " : " VM prête — relevé en cours " ,
" en " : " VM ready - taking its measurements " ,
} ,
" VM start-up " : {
" fr " : " Mise en route de la VM " ,
" en " : " VM start-up " ,
} ,
" system: " : {
" fr " : " système : " ,
" en " : " system: " ,
} ,
" kernel: " : {
" fr " : " noyau : " ,
" en " : " kernel: " ,
} ,
" address: " : {
" fr " : " adresse : " ,
" en " : " address: " ,
} ,
" disk: " : {
" fr " : " disque : " ,
" en " : " disk: " ,
} ,
" uptime: " : {
" fr " : " actif : " ,
" en " : " uptime: " ,
} ,
" Watch the VMs start (no install) " : {
" fr " : " Suivre le démarrage des VM (sans installation) " ,
" en " : " Watch the VMs start (no install) " ,
} ,
[ADD] proxmox : déployer des VM sur un hôte Proxmox distant
Nouvelle entrée sous QEMU/KVM, avec l'équivalent de ses dix-sept commandes.
Toute la différence tient en une phrase : l'hyperviseur est ailleurs. On
choisit donc l'hôte — VM QEMU locale, adresse, ou ~/.ssh/config — et on le
vérifie : pveversion le prouve, id/sudo décident du privilège, et une clé
d'hôte inconnue s'enregistre par ssh-keyscan plutôt qu'en désactivant le
contrôle.
Quatre pièges trouvés sur un hôte réel. Une Proxmox installée sur Debian n'a
aucun pont : on en propose un INTERNE, car ajouter l'interface physique
déplace l'adresse de l'hôte et coupe la session — à distance, sans retour.
--- EN ---
A new entry under QEMU/KVM, with the counterpart of its seventeen commands.
The whole difference fits in one sentence: the hypervisor is elsewhere. So the
host is chosen — local QEMU VM, address, or ~/.ssh/config — and then checked:
pveversion proves it, id/sudo decide about privilege, and an unknown host key
is recorded with ssh-keyscan rather than by disabling the check.
Four traps found on a real host. A Proxmox installed on Debian has no bridge:
we offer an INTERNAL one, because adding the physical NIC moves the host
address and cuts the session — remotely, with no way back.
Assisted-by: Claude Opus 5
2026-08-23 05:54:38 -04:00
" Proxmox VE - Deploy a VM on a remote host " : {
[ADD] proxmox : un écran pour déployer sur un hôte distant
Le déploiement Proxmox se faisait par vingt questions, l'une après
l'autre, sans jamais voir le plan entier. Il a maintenant le même écran
que QEMU/KVM — catalogue à gauche, plan à droite, totaux dessous — et une
touche pour revenir aux questions. Il n'en redit rien : 690 lignes contre
1 340, tout le reste vient du socle commun.
Ce que Proxmox a en propre est montré AVANT de lancer : le VMID, parce
que l'hôte ne dit « déjà pris » qu'après avoir téléchargé l'image, et
l'adresse qui s'en déduit sur un pont interne. Le stockage et le pont sont
lus sur l'hôte, jamais devinés. L'icône 🗄 distingue l'entrée du menu de
celle qui déploie ici même.
--- EN ---
Deploying on Proxmox meant twenty questions, one after another, never
seeing the whole plan. It now has the same screen as QEMU/KVM — catalogue
left, plan right, totals below — and a key to fall back to the questions.
It repeats none of it: 690 lines against 1,340, everything else comes
from the shared foundation.
What belongs to Proxmox is shown BEFORE launching: the VMID, because the
host only says "already taken" after downloading the image, and the
address derived from it on an internal bridge. Storage and bridge are read
from the host, never guessed. The 🗄 icon tells the menu entry apart from
the one that deploys right here.
Assisted-by: Claude Opus 5
2026-08-23 18:01:28 -04:00
# 🗄 et non 💻 : l'entrée d'à côté déploie sur CETTE machine, celle-ci
# sur une baie ailleurs. L'icône est ce qui distingue les deux d'un
# coup d'œil dans le menu.
" fr " : " 🗄 Proxmox VE - Déployer une VM sur un hôte distant " ,
" en " : " 🗄 Proxmox VE - Deploy a VM on a remote host " ,
[ADD] proxmox : déployer des VM sur un hôte Proxmox distant
Nouvelle entrée sous QEMU/KVM, avec l'équivalent de ses dix-sept commandes.
Toute la différence tient en une phrase : l'hyperviseur est ailleurs. On
choisit donc l'hôte — VM QEMU locale, adresse, ou ~/.ssh/config — et on le
vérifie : pveversion le prouve, id/sudo décident du privilège, et une clé
d'hôte inconnue s'enregistre par ssh-keyscan plutôt qu'en désactivant le
contrôle.
Quatre pièges trouvés sur un hôte réel. Une Proxmox installée sur Debian n'a
aucun pont : on en propose un INTERNE, car ajouter l'interface physique
déplace l'adresse de l'hôte et coupe la session — à distance, sans retour.
--- EN ---
A new entry under QEMU/KVM, with the counterpart of its seventeen commands.
The whole difference fits in one sentence: the hypervisor is elsewhere. So the
host is chosen — local QEMU VM, address, or ~/.ssh/config — and then checked:
pveversion proves it, id/sudo decide about privilege, and an unknown host key
is recorded with ssh-keyscan rather than by disabling the check.
Four traps found on a real host. A Proxmox installed on Debian has no bridge:
we offer an INTERNAL one, because adding the physical NIC moves the host
address and cuts the session — remotely, with no way back.
Assisted-by: Claude Opus 5
2026-08-23 05:54:38 -04:00
} ,
" Deploy a virtual machine on Proxmox VE! " : {
" fr " : " Déployer une machine virtuelle sur Proxmox VE ! " ,
" en " : " Deploy a virtual machine on Proxmox VE! " ,
} ,
" Which Proxmox host? " : {
" fr " : " Quel hôte Proxmox ? " ,
" en " : " Which Proxmox host? " ,
} ,
" From the local QEMU VMs " : {
" fr " : " Depuis les VM QEMU locales " ,
" en " : " From the local QEMU VMs " ,
} ,
" Type an address " : {
" fr " : " Saisir une adresse " ,
" en " : " Type an address " ,
} ,
" Keep " : {
" fr " : " Garder " ,
" en " : " Keep " ,
} ,
" Address (user@host, default user root): " : {
" fr " : " Adresse (utilisateur@hôte, utilisateur root par défaut) : " ,
" en " : " Address (user@host, default user root): " ,
} ,
" SSH jump host (blank = none): " : {
" fr " : " Rebond SSH (vide = aucun) : " ,
" en " : " SSH jump host (blank = none): " ,
} ,
" Local VMs: " : {
" fr " : " VM locales : " ,
" en " : " Local VMs: " ,
} ,
" No IP for this VM: is it running? " : {
" fr " : " Pas d ' IP pour cette VM : est-elle démarrée ? " ,
" en " : " No IP for this VM: is it running? " ,
} ,
" Checking " : {
" fr " : " Vérification de " ,
" en " : " Checking " ,
} ,
" Not a Proxmox host (or unreachable): " : {
" fr " : " Ce n ' est pas un hôte Proxmox (ou il est injoignable) : " ,
" en " : " Not a Proxmox host (or unreachable): " ,
} ,
[FIX] proxmox : distinguer « injoignable » de « pas Proxmox »
Rapporté : « je n'arrive pas à me connecter, pourtant il est accessible ».
La machine répondait bel et bien — c'est Proxmox VE qui n'y était pas. Un
seul message couvrait les deux pannes, et la seule ligne montrée en preuve
était l'avertissement de ssh sur la clé d'hôte, qui envoyait chercher un
problème de réseau inexistant.
On demande donc à ssh s'il passe avant de conclure, et le bruit de la clé
d'hôte ne sort plus comme diagnostic. Machine joignable sans Proxmox : la
commande qui l'installe est affichée telle quelle. Vérifié sur les deux VM
du parc — l'une répond sans pveversion, l'autre ne répond plus.
--- EN ---
Reported: "I cannot connect, yet it is reachable". The machine did answer —
Proxmox VE simply was not on it. One message covered both failures, and the
only line shown as evidence was ssh's host-key warning, which sent the
reader looking for a network problem that did not exist.
So we now ask ssh whether it gets through before concluding, and the
host-key noise no longer comes out as a diagnosis. Reachable without
Proxmox: the command that installs it is printed as is. Checked against both
VMs here — one answers without pveversion, the other no longer answers.
Assisted-by: Claude Opus 5
2026-08-24 01:44:01 -04:00
" Reachable, but Proxmox VE is not there: " : {
" fr " : " Machine joignable, mais Proxmox VE n ' y est pas : " ,
" en " : " Reachable, but Proxmox VE is not there: " ,
} ,
" SSH does not get through: " : {
" fr " : " SSH ne passe pas : " ,
" en " : " SSH does not get through: " ,
} ,
" Install it: " : {
" fr " : " Pour l ' installer : " ,
" en " : " Install it: " ,
} ,
" Or redeploy the VM with the hypervisor profile. " : {
" fr " : " Ou redéployer la VM avec le profil hyperviseur. " ,
" en " : " Or redeploy the VM with the hypervisor profile. " ,
} ,
[ADD] proxmox : déployer des VM sur un hôte Proxmox distant
Nouvelle entrée sous QEMU/KVM, avec l'équivalent de ses dix-sept commandes.
Toute la différence tient en une phrase : l'hyperviseur est ailleurs. On
choisit donc l'hôte — VM QEMU locale, adresse, ou ~/.ssh/config — et on le
vérifie : pveversion le prouve, id/sudo décident du privilège, et une clé
d'hôte inconnue s'enregistre par ssh-keyscan plutôt qu'en désactivant le
contrôle.
Quatre pièges trouvés sur un hôte réel. Une Proxmox installée sur Debian n'a
aucun pont : on en propose un INTERNE, car ajouter l'interface physique
déplace l'adresse de l'hôte et coupe la session — à distance, sans retour.
--- EN ---
A new entry under QEMU/KVM, with the counterpart of its seventeen commands.
The whole difference fits in one sentence: the hypervisor is elsewhere. So the
host is chosen — local QEMU VM, address, or ~/.ssh/config — and then checked:
pveversion proves it, id/sudo decide about privilege, and an unknown host key
is recorded with ssh-keyscan rather than by disabling the check.
Four traps found on a real host. A Proxmox installed on Debian has no bridge:
we offer an INTERNAL one, because adding the physical NIC moves the host
address and cuts the session — remotely, with no way back.
Assisted-by: Claude Opus 5
2026-08-23 05:54:38 -04:00
" Check the address, the SSH access and pveversion. " : {
" fr " : " Vérifier l ' adresse, l ' accès SSH et pveversion. " ,
" en " : " Check the address, the SSH access and pveversion. " ,
} ,
" Proxmox host: " : {
" fr " : " Hôte Proxmox : " ,
" en " : " Proxmox host: " ,
} ,
" through " : {
" fr " : " par " ,
" en " : " through " ,
} ,
" exit code " : {
" fr " : " code de retour " ,
" en " : " exit code " ,
} ,
" No VM on this Proxmox host. " : {
" fr " : " Aucune VM sur cet hôte Proxmox. " ,
" en " : " No VM on this Proxmox host. " ,
} ,
" No running VM on this Proxmox host. " : {
" fr " : " Aucune VM démarrée sur cet hôte Proxmox. " ,
" en " : " No running VM on this Proxmox host. " ,
} ,
" VMs on this host: " : {
" fr " : " VM de cet hôte : " ,
" en " : " VMs on this host: " ,
} ,
" Selection (number): " : {
" fr " : " Sélection (numéro) : " ,
" en " : " Selection (number): " ,
} ,
" Deploy a VM on the Proxmox host " : {
" fr " : " Déployer une VM sur l ' hôte Proxmox " ,
" en " : " Deploy a VM on the Proxmox host " ,
} ,
" Preview a deployment (dry-run, nothing sent) " : {
" fr " : " Prévisualiser un déploiement (dry-run, rien n ' est envoyé) " ,
" en " : " Preview a deployment (dry-run, nothing sent) " ,
} ,
" Download a cloud image on the host " : {
" fr " : " Télécharger une image cloud sur l ' hôte " ,
" en " : " Download a cloud image on the host " ,
} ,
" List VMs (qm list) " : {
" fr " : " Lister les VM (qm list) " ,
" en " : " List VMs (qm list) " ,
} ,
" Clean up (orphan disks) " : {
" fr " : " Nettoyer (disques orphelins) " ,
" en " : " Clean up (orphan disks) " ,
} ,
" Statistics (host and VMs) " : {
" fr " : " Statistiques (hôte et VM) " ,
" en " : " Statistics (host and VMs) " ,
} ,
" Remote desktop tunnel (VNC/RDP over SSH) " : {
" fr " : " Tunnel bureau distant (VNC/RDP par SSH) " ,
" en " : " Remote desktop tunnel (VNC/RDP over SSH) " ,
} ,
" List available images and their specs " : {
" fr " : " Lister les images disponibles et leurs specs " ,
" en " : " List available images and their specs " ,
} ,
" Proxmox - example sequence (dry-run) " : {
" fr " : " Proxmox - exemple de séquence (dry-run) " ,
" en " : " Proxmox - example sequence (dry-run) " ,
} ,
" Host " : {
" fr " : " Hôte " ,
" en " : " Host " ,
} ,
" Change the Proxmox host " : {
" fr " : " Changer d ' hôte Proxmox " ,
" en " : " Change the Proxmox host " ,
} ,
" VM name (default: erplibre-<distro>): " : {
" fr " : " Nom de la VM (défaut : erplibre-<distro>) : " ,
" en " : " VM name (default: erplibre-<distro>): " ,
} ,
" RAM in MB, blank = 4096 " : {
" fr " : " RAM en Mo, vide = 4096 " ,
" en " : " RAM in MB, blank = 4096 " ,
} ,
" vCPU, blank = 2 " : {
" fr " : " vCPU, vide = 2 " ,
" en " : " vCPU, blank = 2 " ,
} ,
" Disk size (default 32G): " : {
" fr " : " Taille du disque (défaut 32G) : " ,
" en " : " Disk size (default 32G): " ,
} ,
" Size (+10G to add, 40G for a target): " : {
" fr " : " Taille (+10G pour ajouter, 40G pour une cible) : " ,
" en " : " Size (+10G to add, 40G for a target): " ,
} ,
[FIX] proxmox : pmxcfs sans adresse routable, et le stockage vide
Rapporté sur un Proxmox imbriqué. « pvesm » ne parle qu'à travers /etc/pve,
monté par pmxcfs ; pmxcfs à terre, la commande répond « Connection refused »,
la liste est vide, et l'écran s'arrête sur « aucun stockage » — trois étages
au-dessus du défaut.
pmxcfs ne démarrait pas parce que le nom d'hôte ne résolvait que vers
127.0.1.1, et il cherche une adresse ROUTABLE. L'installation corrige bien
/etc/hosts, mais l'image cloud règle « manage_etc_hosts: True » : cloud-init
le réécrit à CHAQUE démarrage. C'est le redémarrage désormais automatique qui
l'a révélé — l'installation corrigeait, le reboot amorçait le bon noyau, et
cloud-init défaisait la correction dans le même mouvement. Un fichier de
surcharge le gèle.
Deuxième geste manquant : systemd marque pve-cluster « failed » après cinq
essais rapprochés et n'y revient JAMAIS seul. Corriger /etc/hosts ne suffisait
donc pas ; l'installation relance l'unité et CONSTATE le montage plutôt que de
le supposer.
Et l'écran nomme maintenant la cause quand il n'a pas de stockage, plutôt que
de laisser chercher.
Un détail qui aurait fait un faux diagnostic : la sonde de montage
n'interroge pas storage.cfg. Ce fichier N'EXISTE PAS sur une installation
neuve — Proxmox se contente alors de ses stockages par défaut, et « local »
répond parfaitement. Vérifié sur l'hôte : /etc/pve monté, storage.cfg absent,
« pvesm status » rendant local avec 25 Go libres. C'est « .version », fichier
virtuel de pmxcfs, qui fait foi.
--- EN ---
Reported on a nested Proxmox. "pvesm" only speaks through /etc/pve, mounted by
pmxcfs; with pmxcfs down the command answers "Connection refused", the list is
empty, and the screen stops at "no storage" — three floors above the defect.
pmxcfs would not start because the hostname resolved only to 127.0.1.1, and it
needs a ROUTABLE address. The installer does fix /etc/hosts, but the cloud
image sets "manage_etc_hosts: True": cloud-init rewrites it at EVERY boot. The
now-automatic reboot is what revealed it — the install fixed it, the reboot
booted the right kernel, and cloud-init undid the fix in the same motion. An
override file freezes it.
Second missing step: systemd marks pve-cluster "failed" after five rapid
attempts and never returns to it on its own. Fixing /etc/hosts was therefore
not enough; the installer restarts the unit and VERIFIES the mount rather than
assuming it.
And the screen now names the cause when it has no storage, instead of leaving
you to hunt.
One detail that would have made a false diagnosis: the mount probe does not
ask for storage.cfg. That file DOES NOT EXIST on a fresh install — Proxmox
then uses its default storages, and "local" answers perfectly. Verified on the
host: /etc/pve mounted, storage.cfg absent, "pvesm status" returning local with
25 GB free. It is ".version", a pmxcfs virtual file, that tells the truth.
Assisted-by: Claude Opus 5
(cherry picked from commit 6212853048ba8154f2833744e45076d70bd33c72)
2026-08-25 04:30:48 -04:00
" pve-cluster is down: /etc/pve is not mounted. " : {
" fr " : " pve-cluster est à terre : /etc/pve n ' est pas monté. " ,
" en " : " pve-cluster is down: /etc/pve is not mounted. " ,
} ,
" Without it pvesm answers nothing, hence no storage. " : {
" fr " : " Sans lui, pvesm ne répond rien — d ' où l ' absence de stockage. " ,
" en " : " Without it pvesm answers nothing, hence no storage. " ,
} ,
" The hostname only resolves to " : {
" fr " : " Le nom d ' hôte ne résout que vers " ,
" en " : " The hostname only resolves to " ,
} ,
" pmxcfs needs a routable address. " : {
" fr " : " pmxcfs exige une adresse routable. " ,
" en " : " pmxcfs needs a routable address. " ,
} ,
" cloud-init rewrites /etc/hosts at every boot. " : {
" fr " : " cloud-init réécrit /etc/hosts à chaque démarrage. " ,
" en " : " cloud-init rewrites /etc/hosts at every boot. " ,
} ,
" replay install_proxmox.sh on the host: it fixes /etc/hosts and stops cloud-init undoing it. " : {
" fr " : " rejouer install_proxmox.sh sur l ' hôte : il corrige /etc/hosts et empêche cloud-init de le défaire. " ,
" en " : " replay install_proxmox.sh on the host: it fixes /etc/hosts and stops cloud-init undoing it. " ,
} ,
[ADD] proxmox : déployer des VM sur un hôte Proxmox distant
Nouvelle entrée sous QEMU/KVM, avec l'équivalent de ses dix-sept commandes.
Toute la différence tient en une phrase : l'hyperviseur est ailleurs. On
choisit donc l'hôte — VM QEMU locale, adresse, ou ~/.ssh/config — et on le
vérifie : pveversion le prouve, id/sudo décident du privilège, et une clé
d'hôte inconnue s'enregistre par ssh-keyscan plutôt qu'en désactivant le
contrôle.
Quatre pièges trouvés sur un hôte réel. Une Proxmox installée sur Debian n'a
aucun pont : on en propose un INTERNE, car ajouter l'interface physique
déplace l'adresse de l'hôte et coupe la session — à distance, sans retour.
--- EN ---
A new entry under QEMU/KVM, with the counterpart of its seventeen commands.
The whole difference fits in one sentence: the hypervisor is elsewhere. So the
host is chosen — local QEMU VM, address, or ~/.ssh/config — and then checked:
pveversion proves it, id/sudo decide about privilege, and an unknown host key
is recorded with ssh-keyscan rather than by disabling the check.
Four traps found on a real host. A Proxmox installed on Debian has no bridge:
we offer an INTERNAL one, because adding the physical NIC moves the host
address and cuts the session — remotely, with no way back.
Assisted-by: Claude Opus 5
2026-08-23 05:54:38 -04:00
" No storage able to hold a VM disk. " : {
" fr " : " Aucun stockage capable d ' héberger un disque de VM. " ,
" en " : " No storage able to hold a VM disk. " ,
} ,
[FIX] proxmox : le pont NAT s'écrivait avant de savoir si le NAT existe
« Table does not exist » : six lignes d'iptables et « code de retour 1 »,
après avoir déjà posé la strophe dans /etc/network/interfaces. Rien dans ce
bruit ne dit qu'il faut redémarrer.
L'hôte tournait le noyau cloud de Debian, qui est dépouillé de tout netfilter
— aucun module NAT, ni legacy ni nft. Et le cas n'a rien d'exotique : c'est
notre propre install_proxmox.sh qui le produit. Il pose le noyau Proxmox sans
redémarrer, à raison — lancé par ssh, un reboot couperait la session et ferait
passer l'installation pour un échec. Une Proxmox imbriquée fraîchement
installée est donc TOUJOURS dans cet état.
L'avertissement sur le noyau existait déjà, mais à la CONFIRMATION de l'hôte,
et l'hôte est ensuite mémorisé : on revient des jours plus tard créer un pont,
et plus personne ne rappelle rien. Le garde va donc là où la conséquence
tombe, et AVANT toute écriture. Il interroge la table NAT elle-même et non le
NOM du noyau — « -pve » est un indice, pas une preuve — puis nomme le noyau
en cours, celui qui est posé, et la commande qui règle l'affaire.
Le sommaire de déploiement le dit désormais aussi, tant qu'on lit encore
l'écran plutôt qu'au bout d'un journal d'une heure.
--- EN ---
"Table does not exist": six lines of iptables and "exit code 1", after the
stanza had already been written into /etc/network/interfaces. Nothing in that
noise says a reboot is needed.
The host was running Debian's cloud kernel, stripped of all netfilter — no NAT
module, legacy or nft. And the case is not exotic: our own install_proxmox.sh
produces it. It installs the Proxmox kernel without rebooting, rightly — run
over ssh, a reboot would cut the session and make the install look failed. A
freshly installed nested Proxmox is therefore ALWAYS in this state.
The kernel warning already existed, but at host CONFIRMATION, and the host is
then remembered: you come back days later to create a bridge and nothing
reminds you. So the guard moves to where the consequence lands, and BEFORE any
write. It asks the NAT table itself rather than the kernel's NAME — "-pve" is
a hint, not a proof — then names the running kernel, the installed one, and
the command that settles it.
The deployment summary now says it too, while the screen is still being read
rather than at the end of an hour-long log.
Assisted-by: Claude Opus 5
2026-08-25 02:16:54 -04:00
" No NAT table on this host: the bridge would lead nowhere. " : {
" fr " : " Pas de table NAT sur cet hôte : le pont ne mènerait nulle part. " ,
" en " : " No NAT table on this host: the bridge would lead nowhere. " ,
} ,
" reboot it to boot the Proxmox kernel: " : {
" fr " : " à redémarrer pour amorcer le noyau Proxmox : " ,
" en " : " reboot it to boot the Proxmox kernel: " ,
} ,
[ADD] suivi : le redémarrage fait partie de l'installation de Proxmox
Proxmox VE n'existe qu'après un redémarrage : tant que la VM tourne le noyau
de son image cloud, elle n'a aucun module netfilter — ni pont NAT, ni invité.
install_proxmox.sh pose le noyau puis s'arrête, à raison, car lancé par ssh un
reboot couperait sa session et ferait passer l'installation pour un échec. On
le découvrait donc des jours plus tard, en créant un pont.
Le redémarrage revient à l'enveloppe de lancement, qui tourne sur NOTRE
machine et survit à celui de la VM : installation, reboot, attente, puis
vérification du noyau. Le ✅ ne s'écrit qu'après, et il veut donc dire
« hyperviseur utilisable ».
Trois choix méritent d'être dits. On ne redémarre qu'après un SUCCÈS —
redémarrer après un échec effacerait la seule machine sur laquelle on pouvait
chercher. On n'attend pas que ssh « revienne » mais que « uname -r » porte le
motif attendu : sshd répond encore une seconde ou deux après l'ordre, et on
lirait l'ancien noyau en croyant avoir la réponse. Et l'absence du noyau
attendu est un vrai ÉCHEC, pas un avertissement.
Le shell est exécuté par les tests, ssh bouchonné, dans les quatre cas — dont
celui où les deux premières lectures rendent l'ancien noyau. Un garde qu'on ne
sait pas éprouver s'ouvre le jour où il casse.
La note du sommaire ne paraît plus que sans suivi, où rien ne redémarre :
réclamer un redémarrage déjà fait est une consigne fausse.
--- EN ---
Proxmox VE only exists after a reboot: while the VM runs its cloud image's
kernel it has no netfilter module — no NAT bridge, no guest.
install_proxmox.sh installs the kernel then stops, rightly, since run over ssh
a reboot would cut its own session and make the install look failed. So you
found out days later, when creating a bridge.
The reboot moves to the launch wrapper, which runs on OUR machine and survives
the VM's: install, reboot, wait, then verify the kernel. The ✅ is written only
after, and therefore means "usable hypervisor".
Three choices worth stating. We reboot only after SUCCESS — rebooting after a
failure would wipe the one machine you could investigate. We do not wait for
ssh to "come back" but for "uname -r" to carry the expected pattern: sshd
answers for another second or two after the order, and we would read the old
kernel believing we had the answer. And a missing expected kernel is a real
FAILURE, not a warning.
The shell is executed by the tests, ssh stubbed, in all four cases — including
the one where the first two reads return the old kernel. A guard you cannot
exercise opens the day it breaks.
The summary note now appears only without monitoring, where nothing reboots:
asking for a reboot already done is a false instruction.
Assisted-by: Claude Opus 5
2026-08-25 02:49:02 -04:00
" Rebooting to boot the new kernel " : {
" fr " : " Redémarrage pour amorcer le nouveau noyau " ,
" en " : " Rebooting to boot the new kernel " ,
} ,
" waiting for the machine to come back " : {
" fr " : " attente du retour de la machine " ,
" en " : " waiting for the machine to come back " ,
} ,
" kernel booted: " : {
" fr " : " noyau amorcé : " ,
" en " : " kernel booted: " ,
} ,
" the machine did not come back on the expected kernel: " : {
" fr " : " la machine n ' est pas revenue sur le noyau attendu : " ,
" en " : " the machine did not come back on the expected kernel: " ,
} ,
[FIX] proxmox : le pont NAT s'écrivait avant de savoir si le NAT existe
« Table does not exist » : six lignes d'iptables et « code de retour 1 »,
après avoir déjà posé la strophe dans /etc/network/interfaces. Rien dans ce
bruit ne dit qu'il faut redémarrer.
L'hôte tournait le noyau cloud de Debian, qui est dépouillé de tout netfilter
— aucun module NAT, ni legacy ni nft. Et le cas n'a rien d'exotique : c'est
notre propre install_proxmox.sh qui le produit. Il pose le noyau Proxmox sans
redémarrer, à raison — lancé par ssh, un reboot couperait la session et ferait
passer l'installation pour un échec. Une Proxmox imbriquée fraîchement
installée est donc TOUJOURS dans cet état.
L'avertissement sur le noyau existait déjà, mais à la CONFIRMATION de l'hôte,
et l'hôte est ensuite mémorisé : on revient des jours plus tard créer un pont,
et plus personne ne rappelle rien. Le garde va donc là où la conséquence
tombe, et AVANT toute écriture. Il interroge la table NAT elle-même et non le
NOM du noyau — « -pve » est un indice, pas une preuve — puis nomme le noyau
en cours, celui qui est posé, et la commande qui règle l'affaire.
Le sommaire de déploiement le dit désormais aussi, tant qu'on lit encore
l'écran plutôt qu'au bout d'un journal d'une heure.
--- EN ---
"Table does not exist": six lines of iptables and "exit code 1", after the
stanza had already been written into /etc/network/interfaces. Nothing in that
noise says a reboot is needed.
The host was running Debian's cloud kernel, stripped of all netfilter — no NAT
module, legacy or nft. And the case is not exotic: our own install_proxmox.sh
produces it. It installs the Proxmox kernel without rebooting, rightly — run
over ssh, a reboot would cut the session and make the install look failed. A
freshly installed nested Proxmox is therefore ALWAYS in this state.
The kernel warning already existed, but at host CONFIRMATION, and the host is
then remembered: you come back days later to create a bridge and nothing
reminds you. So the guard moves to where the consequence lands, and BEFORE any
write. It asks the NAT table itself rather than the kernel's NAME — "-pve" is
a hint, not a proof — then names the running kernel, the installed one, and
the command that settles it.
The deployment summary now says it too, while the screen is still being read
rather than at the end of an hour-long log.
Assisted-by: Claude Opus 5
2026-08-25 02:16:54 -04:00
" Running kernel: " : {
" fr " : " Noyau en cours : " ,
" en " : " Running kernel: " ,
} ,
" The distribution kernel carries no netfilter module. " : {
" fr " : " Le noyau de la distribution ne porte aucun module netfilter. " ,
" en " : " The distribution kernel carries no netfilter module. " ,
} ,
" Proxmox kernel installed: " : {
" fr " : " Noyau Proxmox installé : " ,
" en " : " Proxmox kernel installed: " ,
} ,
" taken at next boot " : {
" fr " : " pris au prochain démarrage " ,
" en " : " taken at next boot " ,
} ,
" then come back here. " : {
" fr " : " puis revenir ici. " ,
" en " : " then come back here. " ,
} ,
" No Proxmox kernel installed: finish the install first. " : {
" fr " : " Aucun noyau Proxmox installé : terminer l ' installation d ' abord. " ,
" en " : " No Proxmox kernel installed: finish the install first. " ,
} ,
[FIX] proxmox : le pont interne prenait l'adresse de sa propre passerelle
Un Proxmox dans un Proxmox hérite du réseau interne de son parent : la VM
vivait en 10.10.10.152, passerelle 10.10.10.1. Le pont interne, lui, avait son
adresse CODÉE EN DUR à 10.10.10.1/24. Lui demander de la poser sur son propre
pont, c'est prendre l'adresse de sa passerelle et rendre tout le /24 local. La
machine s'isole au milieu de la commande qui la configure : « ifup » n'a jamais
rendu la main, la VM ne répondait plus ni en ssh ni en ping.
Le réseau est donc CHOISI, d'après ce que l'hôte connaît déjà — ses adresses et
ses routes, car une route sans adresse locale suffit à créer le conflit, et la
route par défaut en est l'exemple exact. Le chevauchement se calcule sur les
réseaux et non sur les trois premiers octets : « 10.0.0.0/8 » écarte alors bien
tous les candidats en 10.x. Plus aucun libre ? On le dit, plutôt que d'en
écraser un — écraser, ici, c'est couper la seule voie d'accès.
Le repli « ifreload -a » s'en va aussi. Il rechargeait TOUTES les interfaces, y
compris celle qui porte la session, et sur une image cloud l'interface
principale est décrite ailleurs — ifupdown2 la descend sans la remonter. Le
repli monte maintenant le pont à la main, sans toucher à rien d'autre ; la
strophe le rend persistant. La règle de masquerading se teste avant de
s'ajouter, donc une reprise n'empile rien.
Le test d'origine interdisait « 2>/dev/null » sur toute la ligne pour que
l'erreur d'ifup reste lisible. L'intention est gardée, portée sur l'appel à
ifup seul : le repli, lui, sonde légitimement.
--- EN ---
Proxmox inside Proxmox inherits its parent's internal network: the VM lived at
10.10.10.152, gateway 10.10.10.1. The internal bridge had its address HARDCODED
to 10.10.10.1/24. Asking it to put that on its own bridge takes its gateway's
address and makes the whole /24 local. The machine isolates itself in the
middle of the command configuring it: "ifup" never returned, the VM answered
neither ssh nor ping.
The subnet is now CHOSEN from what the host already knows — its addresses and
its routes, since a route with no local address is enough to collide, and the
default route is exactly that case. Overlap is computed on networks rather than
on the first three octets, so "10.0.0.0/8" correctly rules out every 10.x
candidate. None left? We say so rather than overwrite one — overwriting here
means cutting the only way in.
The "ifreload -a" fallback goes too. It reloaded ALL interfaces, including the
one carrying the session, and on a cloud image the main interface is described
elsewhere — ifupdown2 takes it down without bringing it back. The fallback now
raises the bridge by hand, touching nothing else; the stanza makes it
persistent. The masquerade rule is checked before being added, so a retry piles
nothing up.
The original test banned "2>/dev/null" across the whole line so ifup's error
stayed readable. That intent is kept, narrowed to the ifup call itself: the
fallback legitimately probes.
Assisted-by: Claude Opus 5
(cherry picked from commit 57991b186cf891b0db6b7228fb626c4c1af317cd)
2026-08-25 03:46:57 -04:00
" No free subnet left for an internal bridge. " : {
" fr " : " Plus aucun réseau libre pour un pont interne. " ,
" en " : " No free subnet left for an internal bridge. " ,
} ,
[ADD] proxmox : déployer des VM sur un hôte Proxmox distant
Nouvelle entrée sous QEMU/KVM, avec l'équivalent de ses dix-sept commandes.
Toute la différence tient en une phrase : l'hyperviseur est ailleurs. On
choisit donc l'hôte — VM QEMU locale, adresse, ou ~/.ssh/config — et on le
vérifie : pveversion le prouve, id/sudo décident du privilège, et une clé
d'hôte inconnue s'enregistre par ssh-keyscan plutôt qu'en désactivant le
contrôle.
Quatre pièges trouvés sur un hôte réel. Une Proxmox installée sur Debian n'a
aucun pont : on en propose un INTERNE, car ajouter l'interface physique
déplace l'adresse de l'hôte et coupe la session — à distance, sans retour.
--- EN ---
A new entry under QEMU/KVM, with the counterpart of its seventeen commands.
The whole difference fits in one sentence: the hypervisor is elsewhere. So the
host is chosen — local QEMU VM, address, or ~/.ssh/config — and then checked:
pveversion proves it, id/sudo decide about privilege, and an unknown host key
is recorded with ssh-keyscan rather than by disabling the check.
Four traps found on a real host. A Proxmox installed on Debian has no bridge:
we offer an INTERNAL one, because adding the physical NIC moves the host
address and cuts the session — remotely, with no way back.
Assisted-by: Claude Opus 5
2026-08-23 05:54:38 -04:00
" No network bridge on this host. " : {
" fr " : " Aucun pont réseau sur cet hôte. " ,
" en " : " No network bridge on this host. " ,
} ,
[ADD] proxmox : un écran pour déployer sur un hôte distant
Le déploiement Proxmox se faisait par vingt questions, l'une après
l'autre, sans jamais voir le plan entier. Il a maintenant le même écran
que QEMU/KVM — catalogue à gauche, plan à droite, totaux dessous — et une
touche pour revenir aux questions. Il n'en redit rien : 690 lignes contre
1 340, tout le reste vient du socle commun.
Ce que Proxmox a en propre est montré AVANT de lancer : le VMID, parce
que l'hôte ne dit « déjà pris » qu'après avoir téléchargé l'image, et
l'adresse qui s'en déduit sur un pont interne. Le stockage et le pont sont
lus sur l'hôte, jamais devinés. L'icône 🗄 distingue l'entrée du menu de
celle qui déploie ici même.
--- EN ---
Deploying on Proxmox meant twenty questions, one after another, never
seeing the whole plan. It now has the same screen as QEMU/KVM — catalogue
left, plan right, totals below — and a key to fall back to the questions.
It repeats none of it: 690 lines against 1,340, everything else comes
from the shared foundation.
What belongs to Proxmox is shown BEFORE launching: the VMID, because the
host only says "already taken" after downloading the image, and the
address derived from it on an internal bridge. Storage and bridge are read
from the host, never guessed. The 🗄 icon tells the menu entry apart from
the one that deploys right here.
Assisted-by: Claude Opus 5
2026-08-23 18:01:28 -04:00
# --- Écran de déploiement Proxmox VE (formulaire TUI) ---
" Deploy one or more ERPLibre VMs on Proxmox VE! " : {
" fr " : " Déployer une ou plusieurs VM ERPLibre sur Proxmox VE ! " ,
" en " : " Deploy one or more ERPLibre VMs on Proxmox VE! " ,
} ,
" Proxmox host " : {
" fr " : " Hôte Proxmox " ,
" en " : " Proxmox host " ,
} ,
" node " : {
" fr " : " nœud " ,
" en " : " node " ,
} ,
" Proxmox VE " : {
" fr " : " Proxmox VE " ,
" en " : " Proxmox VE " ,
} ,
" Bridge " : {
" fr " : " Pont " ,
" en " : " Bridge " ,
} ,
" First VMID " : {
" fr " : " Premier VMID " ,
" en " : " First VMID " ,
} ,
" Access " : {
" fr " : " Accès " ,
" en " : " Access " ,
} ,
" SSH public key " : {
" fr " : " Clé publique SSH " ,
" en " : " SSH public key " ,
} ,
" Start the VM after creating it " : {
" fr " : " Démarrer la VM après sa création " ,
" en " : " Start the VM after creating it " ,
} ,
" Add an entry to ~/.ssh/config " : {
" fr " : " Ajouter une entrée à ~/.ssh/config " ,
" en " : " Add an entry to ~/.ssh/config " ,
} ,
" Follow the installation (dashboard) " : {
" fr " : " Suivre l ' installation (tableau de bord) " ,
" en " : " Follow the installation (dashboard) " ,
} ,
" Text prompts " : {
" fr " : " Questions texte " ,
" en " : " Text prompts " ,
} ,
" Nothing to deploy. " : {
" fr " : " Rien à déployer. " ,
" en " : " Nothing to deploy. " ,
} ,
" No bridge on the host. " : {
" fr " : " Aucun pont sur l ' hôte. " ,
" en " : " No bridge on the host. " ,
} ,
[ADD] todo : dire la place qui reste sous le plan de déploiement
La ligne de totaux annonçait « ~126 G » sans dire sur quoi : la demande
seule ne dit pas si ça rentre, et on l'apprenait au déploiement. Elle dit
maintenant la demande, ce qui reste et la capacité — « ~126 G / 20 G libres
sur 270 G » — et prévient dès que le plan dépasse. Les trois limites (RAM,
disque, cœurs) s'affichent ensemble : n'en montrer qu'une cachait les
autres. Sur Proxmox, la place vient du stockage choisi, que « pvesm status »
donnait déjà.
Deux défauts trouvés en le faisant : la marque de génération, exigée de tous
les widgets, faisait taire chaque réglage commun de l'écran Proxmox, et
« [x1] » disparaissait, lu comme une balise Rich.
--- EN ---
The totals line said "~126 G" without saying out of what: the demand alone
does not tell whether it fits, and one found out at deploy time. It now says
the demand, what is left and the capacity — "~126 G / 20 G free of 270 G" —
and warns as soon as the plan exceeds it. The three limits (RAM, disk,
cores) show together: showing only one hid the others. On Proxmox the room
comes from the chosen storage, which "pvesm status" already gave.
Two defects found on the way: the generation mark, required of every widget,
silenced each common setting on the Proxmox screen, and "[x1]" vanished,
read as a Rich tag.
Assisted-by: Claude Opus 5
2026-08-24 01:11:09 -04:00
" more disk than the storage has free " : {
" fr " : " plus de disque que le stockage n ' en a de libre " ,
" en " : " more disk than the storage has free " ,
} ,
[ADD] proxmox : un écran pour déployer sur un hôte distant
Le déploiement Proxmox se faisait par vingt questions, l'une après
l'autre, sans jamais voir le plan entier. Il a maintenant le même écran
que QEMU/KVM — catalogue à gauche, plan à droite, totaux dessous — et une
touche pour revenir aux questions. Il n'en redit rien : 690 lignes contre
1 340, tout le reste vient du socle commun.
Ce que Proxmox a en propre est montré AVANT de lancer : le VMID, parce
que l'hôte ne dit « déjà pris » qu'après avoir téléchargé l'image, et
l'adresse qui s'en déduit sur un pont interne. Le stockage et le pont sont
lus sur l'hôte, jamais devinés. L'icône 🗄 distingue l'entrée du menu de
celle qui déploie ici même.
--- EN ---
Deploying on Proxmox meant twenty questions, one after another, never
seeing the whole plan. It now has the same screen as QEMU/KVM — catalogue
left, plan right, totals below — and a key to fall back to the questions.
It repeats none of it: 690 lines against 1,340, everything else comes
from the shared foundation.
What belongs to Proxmox is shown BEFORE launching: the VMID, because the
host only says "already taken" after downloading the image, and the
address derived from it on an internal bridge. Storage and bridge are read
from the host, never guessed. The 🗄 icon tells the menu entry apart from
the one that deploys right here.
Assisted-by: Claude Opus 5
2026-08-23 18:01:28 -04:00
" more RAM than the host has free " : {
" fr " : " plus de RAM que l ' hôte n ' en a de libre " ,
" en " : " more RAM than the host has free " ,
} ,
" Loading (host, storage, bridges, VMs)... " : {
" fr " : " Chargement (hôte, stockage, ponts, VM)… " ,
" en " : " Loading (host, storage, bridges, VMs)... " ,
} ,
" TUI unavailable " : {
" fr " : " Interface graphique indisponible " ,
" en " : " TUI unavailable " ,
} ,
" VM " : {
" fr " : " VM " ,
" en " : " VM " ,
} ,
" VMID " : {
" fr " : " VMID " ,
" en " : " VMID " ,
} ,
" address " : {
" fr " : " adresse " ,
" en " : " address " ,
} ,
" storage " : {
" fr " : " stockage " ,
" en " : " storage " ,
} ,
" other… " : {
" fr " : " autre… " ,
" en " : " other… " ,
} ,
[ADD] proxmox : déployer des VM sur un hôte Proxmox distant
Nouvelle entrée sous QEMU/KVM, avec l'équivalent de ses dix-sept commandes.
Toute la différence tient en une phrase : l'hyperviseur est ailleurs. On
choisit donc l'hôte — VM QEMU locale, adresse, ou ~/.ssh/config — et on le
vérifie : pveversion le prouve, id/sudo décident du privilège, et une clé
d'hôte inconnue s'enregistre par ssh-keyscan plutôt qu'en désactivant le
contrôle.
Quatre pièges trouvés sur un hôte réel. Une Proxmox installée sur Debian n'a
aucun pont : on en propose un INTERNE, car ajouter l'interface physique
déplace l'adresse de l'hôte et coupe la session — à distance, sans retour.
--- EN ---
A new entry under QEMU/KVM, with the counterpart of its seventeen commands.
The whole difference fits in one sentence: the hypervisor is elsewhere. So the
host is chosen — local QEMU VM, address, or ~/.ssh/config — and then checked:
pveversion proves it, id/sudo decide about privilege, and an unknown host key
is recorded with ssh-keyscan rather than by disabling the check.
Four traps found on a real host. A Proxmox installed on Debian has no bridge:
we offer an INTERNAL one, because adding the physical NIC moves the host
address and cuts the session — remotely, with no way back.
Assisted-by: Claude Opus 5
2026-08-23 05:54:38 -04:00
" offered " : {
" fr " : " proposés " ,
" en " : " offered " ,
} ,
" Would run on " : {
" fr " : " Serait exécuté sur " ,
" en " : " Would run on " ,
} ,
" SSH key -> " : {
" fr " : " clé SSH -> " ,
" en " : " SSH key -> " ,
} ,
" Deploy this VM now? (Y/n): " : {
" fr " : " Déployer cette VM maintenant ? (O/n) : " ,
" en " : " Deploy this VM now? (Y/n): " ,
} ,
" SSH key unreadable: " : {
" fr " : " Clé SSH illisible : " ,
" en " : " SSH key unreadable: " ,
} ,
" SSH key not pushed: password login only. " : {
" fr " : " Clé SSH non transmise : connexion par mot de passe seulement. " ,
" en " : " SSH key not pushed: password login only. " ,
} ,
" Step failed, stopping here. " : {
" fr " : " Étape en échec, on s ' arrête ici. " ,
" en " : " Step failed, stopping here. " ,
} ,
" Waiting for the VM address… " : {
" fr " : " Attente de l ' adresse de la VM… " ,
" en " : " Waiting for the VM address… " ,
} ,
" No address yet. Try [6] later. " : {
" fr " : " Pas encore d ' adresse. Réessayer avec [6] plus tard. " ,
" en " : " No address yet. Try [6] later. " ,
} ,
" Install ERPLibre on it? (Y/n): " : {
" fr " : " Y installer ERPLibre ? (O/n) : " ,
" en " : " Install ERPLibre on it? (Y/n): " ,
} ,
" No address from the guest agent. " : {
" fr " : " Aucune adresse rendue par l ' agent invité. " ,
" en " : " No address from the guest agent. " ,
} ,
" Is qemu-guest-agent installed and the VM started? " : {
" fr " : " qemu-guest-agent est-il installé et la VM démarrée ? " ,
" en " : " Is qemu-guest-agent installed and the VM started? " ,
} ,
" Ctrl+O to quit the serial console. " : {
" fr " : " Ctrl+O pour quitter la console série. " ,
" en " : " Ctrl+O to quit the serial console. " ,
} ,
" Proxmox can only GROW a disk, never shrink it. " : {
" fr " : " Proxmox ne sait qu ' AGRANDIR un disque, jamais le rétrécir. " ,
" en " : " Proxmox can only GROW a disk, never shrink it. " ,
} ,
" This also destroys their disks and backups. " : {
" fr " : " Cela détruit aussi leurs disques et leurs sauvegardes. " ,
" en " : " This also destroys their disks and backups. " ,
} ,
" Nothing orphaned. " : {
" fr " : " Rien d ' orphelin. " ,
" en " : " Nothing orphaned. " ,
} ,
" Orphan disks: " : {
" fr " : " Disques orphelins : " ,
" en " : " Orphan disks: " ,
} ,
" Free them? " : {
" fr " : " Les libérer ? " ,
" en " : " Free them? " ,
} ,
" no address, skipped " : {
" fr " : " pas d ' adresse, ignorée " ,
" en " : " no address, skipped " ,
} ,
" unreachable from here. " : {
" fr " : " injoignable d ' ici. " ,
" en " : " unreachable from here. " ,
} ,
" Use [13] to add a ProxyJump entry, then a tunnel. " : {
" fr " : " Utiliser [13] pour l ' entrée ProxyJump, puis un tunnel. " ,
" en " : " Use [13] to add a ProxyJump entry, then a tunnel. " ,
} ,
" Example: demo-vm, Debian 13, on a Proxmox host " : {
" fr " : " Exemple : demo-vm, Debian 13, sur un hôte Proxmox " ,
" en " : " Example: demo-vm, Debian 13, on a Proxmox host " ,
} ,
" storages " : {
" fr " : " stockages " ,
" en " : " storages " ,
} ,
" memory " : {
" fr " : " mémoire " ,
" en " : " memory " ,
} ,
" No address for this VM. " : {
" fr " : " Aucune adresse pour cette VM. " ,
" en " : " No address for this VM. " ,
} ,
" A static address is visible right after creation. " : {
" fr " : " Une adresse fixe est connue dès la création. " ,
" en " : " A static address is visible right after creation. " ,
} ,
" address given at creation: " : {
" fr " : " adresse donnée à la création : " ,
" en " : " address given at creation: " ,
} ,
" qm create needs one. Two ways: " : {
" fr " : " « qm create » en exige un. Deux voies : " ,
" en " : " qm create needs one. Two ways: " ,
} ,
[ADD] migration: réparer les copies de site qui ne savent plus se rendre
`check_cow_views` cherche la rupture qui ARRÊTE la migration : la forme
que l'arch doit avoir change. Deux autres la laissent finir sans un mot.
Un ancrage manquant : une vue héritière fait un xpath sur `//t[@t-set=…]`
que le module a gagné en chemin ; la copie, page d'utilisateur, n'est
jamais réécrite. Un t-call pendant : le gabarit appelé n'existe plus dans
la cible. Mesuré sur une 12 → 18 réelle : /contact rendait 500 depuis le
palier 14 → 15, quatre paliers de silence, et le test de fumée le voyait
sans savoir le nommer.
Le premier se répare depuis la vue module, en gardant la page telle que
son auteur l'a écrite. Le second se retire, et l'humain décide.
--- EN ---
`check_cow_views` looks for the break that STOPS the migration: the shape
the arch must have changes. Two others let it finish without a word.
A missing anchor: a child view xpaths on `//t[@t-set=…]` that the module
gained along the way; the copy, a user page, is never rewritten. A
dangling t-call: the template called no longer exists in the target.
Measured on a real 12 → 18: /contact returned 500 since the 14 → 15 step,
four steps of silence, and the smoke test saw it without naming it.
The first is repaired from the module view, keeping the page as its
author wrote it. The second is removed, and the human decides.
Assisted-by: Claude Opus 5
2026-08-24 02:53:24 -04:00
" create an internal " : {
" fr " : " créer un pont interne " ,
" en " : " create an internal " ,
} ,
[ADD] proxmox : déployer des VM sur un hôte Proxmox distant
Nouvelle entrée sous QEMU/KVM, avec l'équivalent de ses dix-sept commandes.
Toute la différence tient en une phrase : l'hyperviseur est ailleurs. On
choisit donc l'hôte — VM QEMU locale, adresse, ou ~/.ssh/config — et on le
vérifie : pveversion le prouve, id/sudo décident du privilège, et une clé
d'hôte inconnue s'enregistre par ssh-keyscan plutôt qu'en désactivant le
contrôle.
Quatre pièges trouvés sur un hôte réel. Une Proxmox installée sur Debian n'a
aucun pont : on en propose un INTERNE, car ajouter l'interface physique
déplace l'adresse de l'hôte et coupe la session — à distance, sans retour.
--- EN ---
A new entry under QEMU/KVM, with the counterpart of its seventeen commands.
The whole difference fits in one sentence: the hypervisor is elsewhere. So the
host is chosen — local QEMU VM, address, or ~/.ssh/config — and then checked:
pveversion proves it, id/sudo decide about privilege, and an unknown host key
is recorded with ssh-keyscan rather than by disabling the check.
Four traps found on a real host. A Proxmox installed on Debian has no bridge:
we offer an INTERNAL one, because adding the physical NIC moves the host
address and cuts the session — remotely, with no way back.
Assisted-by: Claude Opus 5
2026-08-23 05:54:38 -04:00
" touches no physical NIC " : {
" fr " : " ne touche à aucune interface physique " ,
" en " : " touches no physical NIC " ,
} ,
" do it myself (bridge-ports <nic>, needs console) " : {
" fr " : " le faire moi-même (bridge-ports <nic>, console requise) " ,
" en " : " do it myself (bridge-ports <nic>, needs console) " ,
} ,
" To bridge the LAN, on the host: " : {
" fr " : " Pour ponter le LAN, sur l ' hôte : " ,
" en " : " To bridge the LAN, on the host: " ,
} ,
" This moves the host address: do it from a console. " : {
" fr " : " Cela déplace l ' adresse de l ' hôte : à faire depuis une console. " ,
" en " : " This moves the host address: do it from a console. " ,
} ,
" uplink for NAT " : { " fr " : " sortie pour le NAT " , " en " : " uplink for NAT " } ,
" none " : { " fr " : " aucune " , " en " : " none " } ,
" The bridge did not come up. " : {
" fr " : " Le pont n ' est pas monté. " ,
" en " : " The bridge did not come up. " ,
} ,
" SSH does not know this host key yet. " : {
" fr " : " ssh ne connaît pas encore la clé de cet hôte. " ,
" en " : " SSH does not know this host key yet. " ,
} ,
" Would record: " : {
" fr " : " Enregistrerait : " ,
" en " : " Would record: " ,
} ,
" Record it? " : {
" fr " : " L ' enregistrer ? " ,
" en " : " Record it? " ,
} ,
" No host key obtained. " : {
" fr " : " Aucune clé d ' hôte obtenue. " ,
" en " : " No host key obtained. " ,
} ,
" key(s) recorded in ~/.ssh/known_hosts " : {
" fr " : " clé(s) enregistrée(s) dans ~/.ssh/known_hosts " ,
" en " : " key(s) recorded in ~/.ssh/known_hosts " ,
} ,
" qm needs root: no root, and sudo asks for a password. " : {
" fr " : " qm exige root : ni root, ni sudo sans mot de passe. " ,
" en " : " qm needs root: no root, and sudo asks for a password. " ,
} ,
" Connect as root@, or allow NOPASSWD sudo. " : {
" fr " : " Se connecter en root@, ou autoriser sudo sans mot de passe. " ,
" en " : " Connect as root@, or allow NOPASSWD sudo. " ,
} ,
[ADD] qemu : déployer Proxmox VE (amd64, arm64)
Proxmox ne publie aucune image cloud : son ISO est un installateur qui formate
le disque. On prend donc la voie que l'amont documente lui-même — Proxmox VE
sur Debian — depuis l'image cloud trixie, partagée avec un déploiement
Debian 13 au lieu d'être téléchargée deux fois.
s390x n'y est pas et n'y sera pas par cette voie : le dépôt n'a aucun index
binary-s390x. arm64 y est, officiel depuis PVE 9. Le catalogue le dit AVANT le
déploiement, au lieu d'échouer au premier apt.
Vérifié sur une VM réelle : pve-manager 9.2.11, noyau 7.0.14-12-pve, quatre
services actifs, interface web en HTTP 200.
--- EN ---
Proxmox publishes no cloud image: its ISO is an installer that formats the
disk. So we take the path upstream documents itself — Proxmox VE on Debian —
from the trixie cloud image, shared with a Debian 13 deployment instead of
being downloaded twice.
s390x is not there and will not be by this route: the repository has no
binary-s390x index. arm64 is, official since PVE 9. The catalog says so BEFORE
the deployment rather than failing at the first apt.
Verified on a real VM: pve-manager 9.2.11, kernel 7.0.14-12-pve, four services
active, web UI answering HTTP 200.
Assisted-by: Claude Opus 5
2026-08-23 03:28:03 -04:00
" Proxmox VE hypervisor (no Odoo) " : {
" fr " : " Hyperviseur Proxmox VE (sans Odoo) " ,
" en " : " Proxmox VE hypervisor (no Odoo) " ,
} ,
" Proxmox will NOT be installed: plain Debian VM. " : {
" fr " : " Proxmox ne sera PAS installé : la VM restera une Debian nue. " ,
" en " : " Proxmox will NOT be installed: plain Debian VM. " ,
} ,
" Later, in the VM: " : {
" fr " : " Plus tard, dans la VM : " ,
" en " : " Later, in the VM: " ,
} ,
[FIX] sshfs : n'annoncer un montage que s'il a eu lieu
Après un code 1, le menu affichait « Monté sur … », la commande pour démonter
et celle pour ouvrir le répertoire : le montage n'avait pas eu lieu, et on
cherchait des fichiers dans un répertoire vide. Le succès ne s'affiche plus
qu'en cas de succès, et le point de montage inutilisé est retiré.
L'échec, lui, avait une cause : sshfs lit « a+b » comme un chaînage d'hôtes et
ne consulte jamais ~/.ssh/config pour l'alias entier. Or c'est todo.py qui
nomme les VM « rebond+domaine », et cette seconde moitié est un domaine
libvirt, pas un alias SSH du rebond. On résout donc l'alias par « ssh -G » et
on rend à sshfs une cible qu'il ne peut plus mal lire, ProxyJump comprise.
--- EN ---
After exit code 1, the menu still printed "Mounted on …", the unmount command
and the file-manager command: nothing had been mounted, and one went looking
for files in an empty directory. The success block now only prints on success,
and the unused mount point is removed.
The failure itself had a cause: sshfs reads "a+b" as host chaining and never
consults ~/.ssh/config for the whole alias. But todo.py is what names VMs
"jump+domain", and that second half is a libvirt domain, not an SSH alias on
the jump host. So we resolve the alias with "ssh -G" and hand sshfs a target
it can no longer misread, ProxyJump included.
Assisted-by: Claude Opus 5
2026-08-22 22:32:34 -04:00
" sshfs mount failed. " : {
" fr " : " Le montage sshfs a échoué. " ,
" en " : " sshfs mount failed. " ,
} ,
" Checking SSH access… " : {
" fr " : " Vérification de l ' accès SSH… " ,
" en " : " Checking SSH access… " ,
} ,
" SSH reaches this host: ~/.ssh/config is fine. " : {
" fr " : " SSH joint cet hôte : ~/.ssh/config est bon. " ,
" en " : " SSH reaches this host: ~/.ssh/config is fine. " ,
} ,
" sshfs reads the « + » as host chaining. " : {
" fr " : " sshfs lit le « + » comme un chaînage d ' hôtes. " ,
" en " : " sshfs reads the « + » as host chaining. " ,
} ,
" Run this instead: " : {
" fr " : " À lancer plutôt : " ,
" en " : " Run this instead: " ,
} ,
" ssh -G resolved nothing: check ~/.ssh/config. " : {
" fr " : " « ssh -G » n ' a rien résolu : vérifier ~/.ssh/config. " ,
" en " : " ssh -G resolved nothing: check ~/.ssh/config. " ,
} ,
" Is sshfs (and fuse) installed here? " : {
" fr " : " sshfs (et fuse) sont-ils installés ici ? " ,
" en " : " Is sshfs (and fuse) installed here? " ,
} ,
" SSH fails too: " : {
" fr " : " SSH échoue aussi : " ,
" en " : " SSH fails too: " ,
} ,
" Update ~/.ssh/config, or check the server is up. " : {
" fr " : " Mettre à jour ~/.ssh/config, ou vérifier que le serveur répond. " ,
" en " : " Update ~/.ssh/config, or check the server is up. " ,
} ,
" Check the SSH host and that the server is up. " : {
" fr " : " Vérifier l ' hôte SSH saisi et que le serveur répond. " ,
" en " : " Check the SSH host and that the server is up. " ,
} ,
" unknown host name: check HostName " : {
" fr " : " nom d ' hôte inconnu : vérifier HostName " ,
" en " : " unknown host name: check HostName " ,
} ,
" no answer: is the server up and reachable? " : {
" fr " : " aucune réponse : le serveur est-il allumé et joignable ? " ,
" en " : " no answer: is the server up and reachable? " ,
} ,
" no route: check the network or the ProxyJump " : {
" fr " : " pas de route : vérifier le réseau ou le ProxyJump " ,
" en " : " no route: check the network or the ProxyJump " ,
} ,
" nothing listening on the SSH port " : {
" fr " : " rien n ' écoute sur le port SSH " ,
" en " : " nothing listening on the SSH port " ,
} ,
" authentication refused: check User and key " : {
" fr " : " authentification refusée : vérifier User et la clé " ,
" en " : " authentication refused: check User and key " ,
} ,
" host key changed for this address " : {
" fr " : " la clé d ' hôte a changé pour cette adresse " ,
" en " : " host key changed for this address " ,
} ,
[ADD] suivi : les statistiques de chaque VM (écriture, RAM, disque)
Le tableau disait la durée et la taille du disque. Il ne disait pas si une VM
TRAVAILLAIT : une installation figée et une qui compile s'y ressemblaient.
Trois chiffres par VM, d'un seul appel « virsh domstats » pour tout le parc
(0,03 s) : ce qu'elle écrit, sa RAM occupée/totale, son disque occupé/total.
Le débit est une moyenne sur DIX secondes — le disque d'une installation
travaille par rafales, et l'instantané n'y montrait que des 0 et des pics.
Le ballon mémoire est réarmé au tour lent : sans période de collecte, libvirt
rend le dernier rapport du pilote, vieux d'une demi-heure. Et cinq caractères
récupérés sur trois colonnes trop larges font tenir la ligne en 150 colonnes.
--- EN ---
The table showed elapsed time and disk size. It did not show whether a VM was
WORKING: a stalled install and a compiling one looked alike.
Three numbers per VM, from a single "virsh domstats" call for the whole fleet
(0.03 s): bytes written, RAM used/total, disk used/total. The write figure is
a TEN-second average — an install's disk works in bursts, and the snapshot
showed only zeros and spikes.
The memory balloon is re-armed on the slow tick: with no collection period,
libvirt hands back the driver's last report, half an hour old. And five
characters reclaimed from three oversized columns keep the row inside 150.
Assisted-by: Claude Opus 5
2026-08-22 06:13:45 -04:00
" no statistics yet " : {
" fr " : " pas encore de statistiques " ,
" en " : " no statistics yet " ,
} ,
" 10s average " : {
" fr " : " moyenne 10 s " ,
" en " : " 10s average " ,
} ,
" total written " : {
" fr " : " cumul écrit " ,
" en " : " total written " ,
} ,
[ADD] qemu : régler le mode CPU, les écrans et le réseau d'une VM
Le formulaire matériel ne réglait que vCPU, RAM, 3D et démarrage. Trois
manques : le mode CPU, qui décide si on peut virtualiser DANS la VM, les
écrans du virtio-gpu, et le réseau — dont le passage au pont, seul moyen
d'exposer la VM sur le LAN.
La vram n'est pas offerte : domxml-to-native prouve que QEMU ne la reçoit
jamais sur un virtio-gpu, seul max_outputs y arrive.
Vérifié sur un domaine jetable et un pont d'essai : max_outputs=2, -cpu
host, vnet sur le pont, MAC et emplacement PCI conservés. 486 tests.
--- EN ---
The hardware form only set vCPU, RAM, 3D and autostart. Three gaps: the CPU
mode, which decides whether one can virtualize INSIDE the VM, the virtio-GPU
screens, and the network — including the switch to a bridge, the only way to
put the VM on the LAN.
vram is not offered: domxml-to-native proves QEMU never receives it on a
virtio-GPU, only max_outputs gets through.
Verified on a throwaway domain and a test bridge: max_outputs=2, -cpu host,
vnet on the bridge, MAC and PCI slot preserved. 486 tests.
Assisted-by: Claude Opus 5
2026-08-21 23:13:42 -04:00
" Screens " : {
" fr " : " Écrans " ,
" en " : " Screens " ,
} ,
" bridge " : {
" fr " : " pont " ,
" en " : " bridge " ,
} ,
" this VM has no interface " : {
" fr " : " cette VM n ' a pas d ' interface réseau " ,
" en " : " this VM has no interface " ,
} ,
" CPU mode " : {
" fr " : " Mode CPU " ,
" en " : " CPU mode " ,
} ,
[ADD] qemu : prendre le GPU de l'hôte, et régler le matériel des VM
Une VM graphique sans accélération rend tout par le processeur : le bureau,
et l'émulateur Android qui tourne dedans — 32 % d'images en retard, mesuré.
Le déploiement prend donc le GPU de l'hôte dès qu'un nœud de rendu existe.
Une VM déjà installée n'avait aucune voie : ni vCPU, ni RAM, ni 3D. Le menu
d'état les règle maintenant, pendant qu'elle est éteinte — le seul moment où
libvirt les lit.
Trois pièges du terrain : « --memory N » ne touche que le ballon, l'ajout
d'egl-headless n'est pas idempotent, et son retrait sans cible emporte la
console VNC. Vérifié sur un domaine jetable, 459 tests verts.
--- EN ---
A graphical VM without acceleration renders everything on the CPU: the
desktop, and the Android emulator inside it — 32 % janky frames, measured.
The deployment now takes the host GPU as soon as a render node exists.
An installed VM had no path at all: no vCPU, no RAM, no 3D. The state menu
now sets them while the VM is shut off — the only moment libvirt reads them.
Three field traps: "--memory N" only moves the balloon, adding egl-headless
is not idempotent, and removing it untargeted takes the VNC console with it.
Verified on a throwaway domain, 459 tests green.
Assisted-by: Claude Opus 5
2026-08-21 22:32:56 -04:00
" no render node on the host " : {
" fr " : " aucun nœud de rendu sur l ' hôte " ,
" en " : " no render node on the host " ,
} ,
" this VM has no virtual screen " : {
" fr " : " cette VM n ' a pas d ' écran virtuel " ,
" en " : " this VM has no virtual screen " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" Unknown VM(s): " : {
" fr " : " VM inconnue(s) : " ,
" en " : " Unknown VM(s): " ,
} ,
" Target state: " : {
" fr " : " État cible : " ,
" en " : " Target state: " ,
} ,
" Target environment? " : {
" fr " : " Environnement cible ? " ,
" en " : " Target environment? " ,
} ,
" Development (~/git/erplibre, SELinux relaxed) " : {
" fr " : " Développement (~/git/erplibre, SELinux relâché) " ,
" en " : " Development (~/git/erplibre, SELinux relaxed) " ,
} ,
" Production (/opt/erplibre, SELinux enforced) " : {
" fr " : " Production (/opt/erplibre, SELinux confiné) " ,
" en " : " Production (/opt/erplibre, SELinux enforced) " ,
} ,
" Choice (1-2, default 1): " : {
" fr " : " Choix (1-2, défaut 1) : " ,
" en " : " Choice (1-2, default 1): " ,
} ,
" Open (start) " : {
" fr " : " Ouvrir (démarrer) " ,
" en " : " Open (start) " ,
} ,
" Close (shut down) " : {
" fr " : " Fermer (éteindre) " ,
" en " : " Close (shut down) " ,
} ,
" start " : {
" fr " : " démarrer " ,
" en " : " start " ,
} ,
" shut down " : {
" fr " : " éteindre " ,
" en " : " shut down " ,
} ,
" Apply: " : {
" fr " : " Appliquer : " ,
" en " : " Apply: " ,
} ,
" Confirm for real? (y/N): " : {
" fr " : " Confirmer pour de vrai ? (o/N) : " ,
" en " : " Confirm for real? (y/N): " ,
} ,
" Storage " : {
" fr " : " Stockage " ,
" en " : " Storage " ,
} ,
" total " : {
" fr " : " au total " ,
" en " : " total " ,
} ,
" used " : {
" fr " : " utilisés " ,
" en " : " used " ,
} ,
" Enter +NG to grow, -NG to shrink, or NG for a target size "
" (e.g. +20G, -10G, 60G). " : {
" fr " : " Entrez +NG pour agrandir, -NG pour réduire, ou NG pour une "
" taille cible (ex. +20G, -10G, 60G). " ,
" en " : " Enter +NG to grow, -NG to shrink, or NG for a target size "
" (e.g. +20G, -10G, 60G). " ,
} ,
" Resize: " : {
" fr " : " Redimensionner : " ,
" en " : " Resize: " ,
} ,
" Invalid size. " : {
" fr " : " Taille invalide. " ,
" en " : " Invalid size. " ,
} ,
" No change. " : {
" fr " : " Aucun changement. " ,
" en " : " No change. " ,
} ,
" New virtual size: " : {
" fr " : " Nouvelle taille virtuelle : " ,
" en " : " New virtual size: " ,
} ,
" SHRINKING is DANGEROUS: the guest filesystem is NOT shrunk. "
" Data beyond the new size is LOST. Shrink the guest FS FIRST, "
" and only then shrink here. " : {
" fr " : " RÉDUIRE est DANGEREUX : le système de fichiers invité n ' est "
" PAS réduit. Les données au-delà de la nouvelle taille sont PERDUES. "
" Réduisez d ' ABORD le FS invité, puis seulement ici. " ,
" en " : " SHRINKING is DANGEROUS: the guest filesystem is NOT shrunk. "
" Data beyond the new size is LOST. Shrink the guest FS FIRST, "
" and only then shrink here. " ,
} ,
" Shut the VM off before shrinking (virsh shutdown). " : {
" fr " : " Éteignez la VM avant de réduire (virsh shutdown). " ,
" en " : " Shut the VM off before shrinking (virsh shutdown). " ,
} ,
" Type y to confirm you understand the risk (y/N): " : {
" fr " : " Tapez o pour confirmer que vous comprenez le risque "
" (o/N, défaut : non) : " ,
" en " : " Type y to confirm you understand the risk (y/N): " ,
} ,
" Resize failed (see error above). " : {
" fr " : " Échec du redimensionnement (voir l ' erreur ci-dessus). " ,
" en " : " Resize failed (see error above). " ,
} ,
" Virtual disk resized. " : {
" fr " : " Disque virtuel redimensionné. " ,
" en " : " Virtual disk resized. " ,
} ,
" Grow the guest filesystem now (over SSH)? (y/N): " : {
" fr " : " Étendre le système de fichiers invité maintenant (via SSH) ? "
" (o/N, défaut : non) : " ,
" en " : " Grow the guest filesystem now (over SSH)? (y/N): " ,
} ,
" No IP; grow the guest FS manually once booted. " : {
" fr " : " Pas d ' IP ; étendez le FS invité manuellement après le boot. " ,
" en " : " No IP; grow the guest FS manually once booted. " ,
} ,
" completed " : {
" fr " : " terminées " ,
" en " : " completed " ,
} ,
" deleted " : {
" fr " : " effacée " ,
" en " : " deleted " ,
} ,
" paused " : {
" fr " : " pause " ,
" en " : " paused " ,
} ,
" Waiting for the VM to start (boot + cloud-init) " : {
" fr " : " En attente du démarrage de la VM (boot + cloud-init) " ,
" en " : " Waiting for the VM to start (boot + cloud-init) " ,
} ,
" (an emulated architecture can be slow; this is normal) " : {
" fr " : " (une architecture émulée peut être lente ; c ' est normal) " ,
" en " : " (an emulated architecture can be slow; this is normal) " ,
} ,
" VM ready - starting the ERPLibre install " : {
" fr " : " VM prête — installation ERPLibre en cours " ,
" en " : " VM ready - starting the ERPLibre install " ,
} ,
" Choice (number or name, blank = native): " : {
" fr " : " Choix (numéro ou nom, vide = native) : " ,
" en " : " Choice (number or name, blank = native): " ,
} ,
" Pick exact versions (comma-separated list) " : {
" fr " : " Choisir des versions précises (liste séparée par des virgules) " ,
" en " : " Pick exact versions (comma-separated list) " ,
} ,
" Selection (numbers, ' all ' , ' principal ' or ' granulaire ' , "
" default: all): " : {
" fr " : " Sélection (numéros, « all », « principal » ou « granulaire », "
" défaut : all) : " ,
" en " : " Selection (numbers, ' all ' , ' principal ' or ' granulaire ' , "
" default: all): " ,
} ,
" All versions: " : {
" fr " : " Toutes les versions : " ,
" en " : " All versions: " ,
} ,
" Selection (comma-separated numbers): " : {
" fr " : " Sélection (numéros séparés par des virgules) : " ,
" en " : " Selection (comma-separated numbers): " ,
} ,
" s390x images only exist for: " : {
" fr " : " images s390x disponibles seulement pour : " ,
" en " : " s390x images only exist for: " ,
} ,
" ignored: " : {
" fr " : " ignoré : " ,
" en " : " ignored: " ,
} ,
" IBM Z — emulated, slow on x86 " : {
" fr " : " IBM Z — émulé, lent sur x86 " ,
" en " : " IBM Z — emulated, slow on x86 " ,
} ,
" Choice (number or name, blank = amd64): " : {
" fr " : " Choix (numéro ou nom, vide = amd64) : " ,
" en " : " Choice (number or name, blank = amd64): " ,
} ,
" s390x is emulated (TCG): boot and install are much "
" slower than x86. " : {
" fr " : " s390x est émulé (TCG) : le boot et l ' installation sont bien "
" plus lents que x86. " ,
" en " : " s390x is emulated (TCG): boot and install are much "
" slower than x86. " ,
} ,
" Choice (number or version, blank = default): " : {
" fr " : " Choix (numéro ou version, vide = défaut) : " ,
" en " : " Choice (number or version, blank = default): " ,
} ,
" Invalid selection, using " : {
" fr " : " Sélection invalide, utilisation de " ,
" en " : " Invalid selection, using " ,
} ,
" select all " : {
" fr " : " tout sélectionner " ,
" en " : " select all " ,
} ,
" Delete VM(s) " : {
[ADD] qemu: build the tunnel to the remote desktop
A "Remote desktop tunnel" entry under SSH configuration. It lists the
targets, resolves them, and composes the full command -- nothing to fill
in.
todo.py runs on the libvirt HOST; the tunnel starts from the workstation.
It cannot open it, but it alone knows the VM's private IP, its port and
the address through which it was reached. That last one comes from
SSH_CONNECTION, whose third field is exactly the server address used --
far safer than a "hostname" that may resolve to nothing outside.
When ~/.ssh/config already carries the entry, the tunnel takes it:
"ssh -N -L 5902:localhost:5901 <vm>". ProxyJump makes the route and
"localhost" means the VM's OWN loopback, so the tunnel survives an IP
change. That config is also the right source of targets, not the local
libvirt: a graphical VM is often nested, and virsh would only ever list
the orchestrator.
Two details: the hypervisor console needs VNC bound to the loopback, as
"listen=none" opens no socket at all; and two menu entries had no icon.
--- FR ---
Une entrée « Tunnel bureau distant » sous Configuration SSH. Elle liste
les cibles, les résout, et compose la commande complète — rien à remplir.
todo.py tourne sur l'HÔTE libvirt ; le tunnel, lui, part du poste de
travail. Il ne peut donc pas l'ouvrir, mais il est le seul à connaître
l'IP privée de la VM, son port et l'adresse par laquelle on l'a joint.
Cette dernière vient de SSH_CONNECTION, dont le troisième champ est
exactement l'adresse serveur utilisée — bien plus sûr qu'un « hostname »
qui peut ne rien résoudre depuis l'extérieur.
Quand ~/.ssh/config porte déjà l'entrée, le tunnel l'emprunte :
« ssh -N -L 5902:localhost:5901 <vm> ». Le ProxyJump fait la route et
« localhost » désigne le bouclage DE LA VM, si bien que le tunnel survit à
un changement d'IP. Ce fichier est aussi la bonne source de cibles, pas le
libvirt local : une VM graphique est souvent imbriquée, et virsh ne
listerait jamais que l'orchestrateur.
Deux détails : la console de l'hyperviseur exige un VNC sur la boucle
locale, « listen=none » n'ouvrant aucun socket ; et deux entrées de menu
n'avaient pas d'icône.
Assisted-by: Claude Opus 5
2026-08-13 03:40:22 -04:00
" fr " : " 🗑 Effacer une ou plusieurs VM " ,
" en " : " 🗑 Delete VM(s) " ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
} ,
" No VM found. " : {
" fr " : " Aucune VM trouvée. " ,
" en " : " No VM found. " ,
} ,
" Select VMs to delete: " : {
" fr " : " Sélectionner les VM à effacer : " ,
" en " : " Select VMs to delete: " ,
} ,
" Selection (numbers, or ' all ' ): " : {
" fr " : " Sélection (numéros, ou « all ») : " ,
" en " : " Selection (numbers, or ' all ' ): " ,
} ,
" Also delete disk images (qcow2 + seed ISO)? (y/N): " : {
" fr " : " Effacer aussi les disques (qcow2 + seed ISO) ? (o/N, défaut : non) : " ,
" en " : " Also delete disk images (qcow2 + seed ISO)? (y/N, default: no): " ,
} ,
" Will delete: " : {
" fr " : " Sera effacé : " ,
" en " : " Will delete: " ,
} ,
" disk images and seed ISOs " : {
" fr " : " les disques et les seed ISO " ,
" en " : " disk images and seed ISOs " ,
} ,
" disks kept " : {
" fr " : " disques conservés " ,
" en " : " disks kept " ,
} ,
" Confirm deletion? (y/N): " : {
" fr " : " Confirmer l ' effacement ? (o/N, défaut : non) : " ,
" en " : " Confirm deletion? (y/N, default: no): " ,
} ,
" Deletion done. " : {
" fr " : " Effacement terminé. " ,
" en " : " Deletion done. " ,
} ,
" Clean up QEMU (orphan files) " : {
" fr " : " 🧹 Nettoyer QEMU (fichiers orphelins) " ,
" en " : " 🧹 Clean up QEMU (orphan files) " ,
} ,
" Scanning for orphan QEMU files... " : {
" fr " : " Recherche des fichiers QEMU orphelins... " ,
" en " : " Scanning for orphan QEMU files... " ,
} ,
" orphan disk " : {
" fr " : " disque orphelin " ,
" en " : " orphan disk " ,
} ,
" orphan seed " : {
" fr " : " seed orphelin " ,
" en " : " orphan seed " ,
} ,
" partial download " : {
" fr " : " téléchargement interrompu " ,
" en " : " partial download " ,
} ,
" orphan UEFI nvram " : {
" fr " : " nvram UEFI orpheline " ,
" en " : " orphan UEFI nvram " ,
} ,
" No orphan files found. " : {
" fr " : " Aucun fichier orphelin trouvé. " ,
" en " : " No orphan files found. " ,
} ,
" Orphan files: " : {
" fr " : " Fichiers orphelins : " ,
" en " : " Orphan files: " ,
} ,
[ADD] proxmox : le guide de connexion, qui manquait à toute VM distante
Rapporté sur Arch : « pas l'écran de connexion, avec le guide qui dit de
prendre pacman, comme sur ubuntu ». Ce n'était pas Arch — c'était Proxmox.
La voie libvirt livre /etc/motd par le « write_files » de cloud-init, et
« qm set » n'offre pas cela : AUCUNE VM déployée sur un hôte Proxmox n'avait
de guide, quelle que soit sa distribution.
Une troisième voie de livraison, par ssh une fois la VM debout, avec la MÊME
source — « guide_files », qui connaissait déjà pacman. Le guide n'annonce le
dépôt que s'il y sera : un guide qui promet ce qui n'existe pas est pire que
pas de guide. Écrit sur la VM Arch de l'utilisateur pour le vérifier.
--- EN ---
Reported on Arch: "no login screen, with the guide telling you to use pacman,
like on ubuntu". It was not Arch — it was Proxmox. The libvirt path delivers
/etc/motd through cloud-init's "write_files", and "qm set" offers no such
thing: NO VM deployed on a Proxmox host had a guide, whatever its distro.
A third delivery path, over ssh once the VM is up, from the SAME source —
"guide_files", which already knew pacman. The guide only announces the
repository if it will be there: a guide promising what does not exist is worse
than none. Written onto the user's Arch VM to check it.
Assisted-by: Claude Opus 5
2026-08-24 14:06:25 -04:00
" connection guide written " : {
" fr " : " guide de connexion écrit (/etc/motd) " ,
" en " : " connection guide written (/etc/motd) " ,
} ,
" guide not written " : {
" fr " : " guide non écrit " ,
" en " : " guide not written " ,
} ,
[FIX] proxmox : viser la bonne machine, et dire la vérité sur le disque
« s » ouvrait encore la VM locale homonyme : la vue de progression n'avait
que le NOM de la VM, et l'entrée ~/.ssh/config n'existe pas encore à ce
moment. Le déploiement lui passe maintenant « ssh -J <hôte> user@<ip> », qui
ne dépend de rien. Deux voisines du même défaut, jamais rapportées mais aussi
graves : la console ouvrait « virsh console <nom> » — celle de la VM LOCALE —
et la pause suspendait la locale. Les deux passent par le VMID sur l'hôte.
La colonne Disque annonçait « 6.0G/6.0G » sur une VM qui n'avait écrit que
1,2 Go : « du -sb » rend la taille APPARENTE, et un disque raw creux la donne
entière. « du -sB1 » compte les blocs. Enfin l'écran de déploiement dit ce qui
l'attend : « Quitter (q) pour lancer l'installation d'ERPLibre » — on
attendait devant une fenêtre terminée sans le savoir.
--- EN ---
"s" still opened the homonymous local VM: the progress view only had the VM's
NAME, and the ~/.ssh/config entry does not exist yet at that point. The
deployment now hands it "ssh -J <host> user@<ip>", which depends on nothing.
Two neighbours of the same defect, never reported but just as serious: the
console opened "virsh console <name>" — the LOCAL VM's — and pause suspended
the local one. Both now go through the VMID on the host.
The Disk column claimed "6.0G/6.0G" on a VM that had written 1.2 GB: "du -sb"
returns the APPARENT size, and a sparse raw disk gives it in full. "du -sB1"
counts blocks. Finally the deployment screen says what awaits it: "Quit (q)
to start the ERPLibre install" — one waited before a finished window without
knowing.
Assisted-by: Claude Opus 5
2026-08-24 06:58:02 -04:00
" Quit (q) to start the ERPLibre install " : {
" fr " : " Quitter (q) pour lancer l ' installation d ' ERPLibre " ,
" en " : " Quit (q) to start the ERPLibre install " ,
} ,
" Quit (q) to follow the VM starting up " : {
" fr " : " Quitter (q) pour suivre la mise en route de la VM " ,
" en " : " Quit (q) to follow the VM starting up " ,
} ,
[FIX] proxmox : un seul nom par entrée ~/.ssh/config, et le bon
L'entrée portait deux noms sur sa ligne « Host » — le chaîné « hôte+vm » et
le court : « Host erplibre-proxmox-9+erplibre-arch-latest
erplibre-arch-latest ». Le second est un doublon dès que le premier suffit.
ssh n'a besoin que d'un nom ; le doubler n'ajoute qu'une façon de plus
d'écrire la même adresse. Rapporté.
Un seul, donc, et choisi : le nom court quand il est LIBRE, c'est celui qu'on
tape ; le chaîné quand il désignerait une autre machine — une VM locale
homonyme, ou la VM d'un autre hôte Proxmox. Ce qui a forcé le changement est
nommé à l'écran plutôt que laissé en surprise.
« Pris » se juge sur le ProxyJump du bloc et non sur sa seule présence. Le
test l'a montré avant l'usage : notre propre entrée, réécrite à chaque
déploiement, se prenait pour une rivale et le nom basculait d'une fois sur
l'autre.
--- EN ---
The entry carried two names on its "Host" line — the chained "host+vm" and
the short one: "Host erplibre-proxmox-9+erplibre-arch-latest
erplibre-arch-latest". The second is redundant as soon as the first is
enough. ssh needs one name; doubling it only adds another way to write the
same address. Reported.
One name then, and a chosen one: the short one while it is FREE, since that
is what you type; the chained one when it would point at another machine — a
local VM of the same name, or another Proxmox host's VM. Whatever forced the
change is named on screen rather than left as a surprise.
"Taken" is judged on the block's ProxyJump, not on its mere presence. The
test showed it before use: our own entry, rewritten at every deployment, took
itself for a rival and the name flipped from one run to the next.
Assisted-by: Claude Opus 5
2026-08-24 22:55:46 -04:00
" This name is already taken by " : {
" fr " : " Ce nom est déjà pris par " ,
" en " : " This name is already taken by " ,
} ,
" a local VM " : {
" fr " : " une VM locale " ,
" en " : " a local VM " ,
} ,
[FIX] proxmox : l'installation partait sur la mauvaise machine
Rapporté, et c'est le plus grave de la série. Une VM déployée sur Proxmox
sous le nom « erplibre-ubuntu-2604 » — nom déjà porté par un domaine LOCAL —
a vu son installation d'ERPLibre + Odoo partir sur la VM locale. Deux causes
enchaînées : l'entrée ~/.ssh/config volait l'alias de la locale, et le
lanceur détaché ré-résout l'adresse par virsh à chaque tour, qui a répondu
avec le domaine homonyme. Le journal l'écrivait — « → 192.168.123.118 » —
sans que rien n'alerte.
Une VM distante n'est plus ré-résolue : son alias est la seule vérité,
puisqu'il porte le rebond. Et son alias suit la convention des VM imbriquées,
« hôte+vm », le nom court n'étant ajouté que s'il est libre — l'écran le dit.
S'y ajoute le sommaire final qui manquait, à l'image de QEMU/KVM : ce qui
existe, son adresse, sa commande ssh, son journal.
--- EN ---
Reported, and the worst of the series. A VM deployed on Proxmox under the
name "erplibre-ubuntu-2604" — a name already held by a LOCAL domain — had its
ERPLibre + Odoo install land on the local VM. Two chained causes: the
~/.ssh/config entry stole the local one's alias, and the detached launcher
re-resolves the address through virsh on every pass, which answered with the
homonymous domain. The log said so — "→ 192.168.123.118" — with nothing to
raise an alarm.
A remote VM is no longer re-resolved: its alias is the only truth, since it
carries the jump. And its alias follows the nested-VM convention, "host+vm",
the short name being added only when free — the screen says so. Plus the
final summary that was missing, mirroring QEMU/KVM: what exists, its address,
its ssh command, its log.
Assisted-by: Claude Opus 5
2026-08-24 06:37:56 -04:00
" A local VM already bears this name: " : {
" fr " : " Une VM locale porte déjà ce nom : " ,
" en " : " A local VM already bears this name: " ,
} ,
" the alias goes to " : {
" fr " : " l ' alias devient " ,
" en " : " the alias goes to " ,
} ,
2026-08-24 05:38:28 -04:00
" s ssh · c copy log · C copy all · q quit " : {
" fr " : " s ssh · c copier le log · C copier tout · q quitter " ,
" en " : " s ssh · c copy log · C copy all · q quit " ,
} ,
" No VM created yet. " : {
" fr " : " Aucune VM créée pour l ' instant. " ,
" en " : " No VM created yet. " ,
} ,
[ADD] proxmox : suivre une VM distante, changer son état, étendre le menu
Les trois manques restants, demandés. Les colonnes vivantes du suivi — écrit
par seconde, RAM, disque — venaient de virsh, qui ne connaît pas les VM d'un
hôte Proxmox : elles restaient vides, et le relevé d'état les déclarait même
« effacées », ce qui éteignait le reste. L'hôte sait tout cela en un appel
(« pvesh get /cluster/resources »), et le relevé prend la forme de celui de
virsh pour que rien en aval ne distingue la source. Un appel par hôte, mis en
cache cinq secondes : une poignée de main ssh coûte 1 s, virsh 0,03 s.
« Lister les VM » propose maintenant d'en changer l'état, comme QEMU/KVM —
éteindre proprement avant de couper le courant, l'ordre le dit. Et le menu
accepte les commandes ajoutées par todo.json. Enfin « models » manquait aux
traductions : le rapport de migration sortait « 812 models » en français.
--- EN ---
The three remaining gaps, as asked. The dashboard's live columns — written per
second, RAM, disk — came from virsh, which knows nothing of a Proxmox host's
VMs: they stayed empty, and the state probe even declared them "gone", which
switched off the rest. The host knows all of it in one call ("pvesh get
/cluster/resources"), and the reading takes virsh's own shape so nothing
downstream tells the sources apart. One call per host, cached five seconds: an
ssh handshake costs 1 s, virsh 0.03 s.
"List VMs" now offers to change their state, like QEMU/KVM — clean shutdown
before pulling the plug, the order says so. And the menu accepts the commands
todo.json adds. Finally "models" was missing from the translations: the
migration report printed "812 models" in French.
Assisted-by: Claude Opus 5
2026-08-24 05:11:58 -04:00
# « models » manquait alors que ses trois voisines de la même ligne
# étaient traduites : le rapport de qualité de migration sortait
# « 42 modules · 812 models · 1 204 vues ».
" models " : {
" fr " : " modèles " ,
" en " : " models " ,
} ,
" shutdown (clean) " : {
" fr " : " éteindre (proprement) " ,
" en " : " shutdown (clean) " ,
} ,
" stop (pulls the plug) " : {
" fr " : " arrêter (coupe le courant) " ,
" en " : " stop (pulls the plug) " ,
} ,
" Choice (blank = back): " : {
" fr " : " Choix (vide = retour) : " ,
" en " : " Choice (blank = back): " ,
} ,
" Confirm? (y/N): " : {
" fr " : " Confirmer ? (o/N) : " ,
" en " : " Confirm? (y/N): " ,
} ,
[ADD] proxmox : créer le pont manquant depuis l'écran
Sans pont, « qm create » est impossible — et l'écran refusait de déployer
« aucun pont sur l'hôte » sans offrir le moindre moyen d'en avoir un. Une
Proxmox installée SUR Debian n'en a jamais : l'ISO en crée un, pas la
procédure sur Debian.
Deux moments, donc. Avant l'écran, la question se pose dans le terminal, où
l'on peut expliquer les deux voies et montrer ce qui s'exécute. Dans l'écran,
le sélecteur porte « ➕ créer un pont interne vmbr0 (10.10.10.1/24) + NAT » :
la création part dans un fil, l'affichage reste vivant, et le pont créé se
sélectionne tout seul. Elle ne demande rien parce qu'un pont interne ne
touche à aucune interface physique ; un pont sur le LAN déplace l'adresse de
l'hôte et coupe la session, donc il reste manuel.
--- EN ---
With no bridge, "qm create" is impossible — and the screen refused to deploy
"no bridge on the host" without offering any way to get one. A Proxmox
installed ON Debian never has one: the ISO creates it, the Debian procedure
does not.
Two moments, then. Before the screen, the question is asked in the terminal,
where both ways can be explained and the commands shown. In the screen, the
selector carries "➕ create an internal vmbr0 (10.10.10.1/24) + NAT": creation
runs in a thread, the display stays alive, and the new bridge selects itself.
It asks nothing because an internal bridge touches no physical NIC; a bridge
on the LAN moves the host's address and cuts the session, so it stays manual.
Assisted-by: Claude Opus 5
2026-08-24 04:59:13 -04:00
" Creating the bridge on the host… " : {
" fr " : " Création du pont sur l ' hôte… " ,
" en " : " Creating the bridge on the host… " ,
} ,
[FIX] qemu : ne jamais proposer d'effacer un fichier en usage
Rapporté, et c'est le pire défaut possible ici. Après avoir renommé une VM
en « erplibre-ubuntu-2404-MIGRATION », le nettoyage offrait au « rm -f » son
disque de 63 Go, son seed et son nvram — les trois attachés à une VM EN
MARCHE. L'orphelinat se jugeait sur le NOM du fichier comparé aux noms de
domaines ; un renommage suffisait donc à condamner une VM de production.
L'autorité est désormais libvirt : ce qu'un domaine référence n'est pas
orphelin, quel que soit son nom, et l'écran nomme ce qu'il conserve et pour
qui. Un second contrôle, indépendant, épargne aussi ce qu'un processus tient
ouvert. L'effacement d'une VM demande de même ses fichiers à libvirt : par le
nom, il laissait les 63 Go derrière lui.
--- EN ---
Reported, and it is the worst possible defect here. After renaming a VM to
"erplibre-ubuntu-2404-MIGRATION", cleanup offered its 63 GB disk, its seed
and its nvram to "rm -f" — all three attached to a RUNNING VM. Orphanhood was
judged on the file NAME against domain names; a rename was therefore enough
to condemn a production VM.
Libvirt is now the authority: what a domain references is not an orphan,
whatever its name, and the screen names what it keeps and for whom. A second,
independent check also spares whatever a process holds open. Deleting a VM
likewise asks libvirt for its files: by name, it left the 63 GB behind.
Assisted-by: Claude Opus 5
2026-08-24 04:42:09 -04:00
" no disk file found for this VM " : {
" fr " : " aucun fichier de disque trouvé pour cette VM " ,
" en " : " no disk file found for this VM " ,
} ,
" a running process " : {
" fr " : " un processus en cours " ,
" en " : " a running process " ,
} ,
" Kept (still attached to a VM): " : {
" fr " : " Conservés (encore attachés à une VM) : " ,
" en " : " Kept (still attached to a VM): " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" Total: " : {
" fr " : " Total : " ,
" en " : " Total: " ,
} ,
" files " : {
" fr " : " fichiers " ,
" en " : " files " ,
} ,
" Delete these orphan files? (y/N): " : {
" fr " : " Effacer ces fichiers orphelins ? (o/N, défaut : non) : " ,
" en " : " Delete these orphan files? (y/N, default: no): " ,
} ,
" Cleanup done. " : {
" fr " : " Nettoyage terminé. " ,
" en " : " Cleanup done. " ,
} ,
" Cached base images (reusable): " : {
" fr " : " Images de base en cache (réutilisables) : " ,
" en " : " Cached base images (reusable): " ,
} ,
" Also delete the cached base images? (y/N): " : {
" fr " : " Effacer aussi les images de base en cache ? (o/N, défaut : non) : " ,
" en " : " Also delete the cached base images? (y/N, default: no): " ,
} ,
" All cached base images (reusable): " : {
" fr " : " Toutes les images de base en cache (réutilisables) : " ,
" en " : " All cached base images (reusable): " ,
} ,
" Delete ALL cached base images? (y/N): " : {
" fr " : " Effacer TOUTES les images de base en cache ? (o/N, défaut : non) : " ,
" en " : " Delete ALL cached base images? (y/N, default: no): " ,
} ,
" Ghost domains (defined but disk missing): " : {
" fr " : " Domaines fantômes (définis, disque manquant) : " ,
" en " : " Ghost domains (defined but disk missing): " ,
} ,
" Undefine these ghost domains? (y/N): " : {
" fr " : " Supprimer la définition de ces domaines ? (o/N, défaut : non) : " ,
" en " : " Undefine these ghost domains? (y/N, default: no): " ,
} ,
" Stale codename-named Ubuntu images (duplicates): " : {
" fr " : " Images Ubuntu nommées par codename (doublons) : " ,
" en " : " Stale codename-named Ubuntu images (duplicates): " ,
} ,
" Delete these duplicate images? (y/N): " : {
" fr " : " Effacer ces images en double ? (o/N, défaut : non) : " ,
" en " : " Delete these duplicate images? (y/N, default: no): " ,
} ,
[FIX] nettoyage : les enfants s'en vont avec leur rebond
La VM Proxmox locale effacée, le nettoyage a retiré son entrée ssh — c'était
juste — et GARDÉ les trois entrées qui rebondissaient par elle, en les
annonçant « mènent encore quelque part ». Trois culs-de-sac, désignés comme
vivants.
Deux fautes. Un ProxyJump valait preuve de vie À LUI SEUL, au motif qu'il
désigne une VM imbriquée que virsh ne connaîtra jamais : le raisonnement
oubliait que le rebond, lui, peut avoir disparu. Et chaque entrée était jugée
ISOLÉMENT, alors que retirer le parent orpheline ses enfants — qui
orphelinent les leurs. D'où un point fixe, et non une passe.
Un rebond qu'on ne gère pas — hôte personnel, adresse, nom DNS — reste
supposé vivant : on n'efface pas sur une supposition. Mais un nom de NOTRE
nommage sans entrée et sans domaine ne mène nulle part, et c'est exactement
l'état qu'un nettoyage précédent laisse derrière lui.
La liste des orphelines dit maintenant POURQUOI. « Son rebond n'existe
plus : erplibre-proxmox-9 » est la seule chose qui permet de répondre non en
connaissance de cause.
--- EN ---
With the local Proxmox VM deleted, the cleanup removed its ssh entry — rightly
— and KEPT the three entries hopping through it, announcing them as "still
lead somewhere". Three dead ends, labelled alive.
Two defects. A ProxyJump counted as proof of life ON ITS OWN, on the grounds
that it names a nested VM virsh will never know: the reasoning forgot the jump
itself can be gone. And each entry was judged IN ISOLATION, while removing a
parent orphans its children — which orphan theirs. Hence a fixed point, not a
single pass.
A jump we do not manage — personal host, address, DNS name — stays presumed
alive: we do not delete on a guess. But a name of OUR OWN convention with no
entry and no domain leads nowhere, and that is exactly the state a previous
cleanup leaves behind.
The orphan list now says WHY. "Its jump host is gone: erplibre-proxmox-9" is
the only thing that lets you answer no knowingly.
Assisted-by: Claude Opus 5
2026-08-25 01:00:46 -04:00
" its jump host is gone: " : {
" fr " : " son rebond n ' existe plus : " ,
" en " : " its jump host is gone: " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" Orphan ~/.ssh/config entries: " : {
" fr " : " Entrées ~/.ssh/config orphelines : " ,
" en " : " Orphan ~/.ssh/config entries: " ,
} ,
[FIX] analyse: écarter les champs qui n'ont jamais porté de donnée
Le seau « NON déclarés par OpenUpgrade » comptait 565 champs au palier
16 → 17, dont 397 `__last_update` — un champ magique qu'Odoo 17 cesse
d'inscrire et qui n'a jamais eu de colonne. Un chiffre de tête qui fait
peur pour rien fait ignorer le rapport entier.
`store=false` le dit sans ambiguïté, et `id` s'y ajoute : il porte une
donnée, mais celle de la ligne, pas la sienne. Ils vont dans une
catégorie à part, en teinte calme, et NOMMÉE — « __last_update × 101
modèle(s) » explique à lui seul le gros chiffre.
Mesuré sur la chaîne 12 → 18 : le total passe de 1176 à 491, le pire
palier de 565 à 57. Les quatre vrais champs techniques perdus restent
visibles.
--- EN ---
The « NOT declared by OpenUpgrade » bucket held 565 fields at the 16 → 17
step, 397 of them `__last_update` — a magic field Odoo 17 stops
recording, which never had a column. A headline number that frightens
for nothing gets the whole report ignored.
`store=false` says so unambiguously, and `id` joins it: it carries data,
but the row's, not its own. They go to a separate bucket, in a quiet
tint, and NAMED — « __last_update × 101 model(s) » explains the big
number on its own.
Measured on the 12 → 18 chain: the total drops from 1176 to 491, the
worst step from 565 to 57. The four genuinely lost technical fields stay
visible.
Assisted-by: Claude Opus 5
2026-08-24 05:48:03 -04:00
" Kept (still leads somewhere): " : {
" fr " : " Conservées (mènent encore quelque part) : " ,
" en " : " Kept (still leads somewhere): " ,
} ,
" reached through a jump host " : {
" fr " : " joignable par un rebond " ,
" en " : " reached through a jump host " ,
} ,
" a VM of the Proxmox host " : {
" fr " : " une VM de l ' hôte Proxmox " ,
" en " : " a VM of the Proxmox host " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" Remove these ~/.ssh/config entries? (y/N): " : {
" fr " : " Retirer ces entrées de ~/.ssh/config ? (o/N, défaut : non) : " ,
" en " : " Remove these ~/.ssh/config entries? (y/N, default: no): " ,
} ,
" Stale DHCP leases (no matching VM): " : {
" fr " : " Baux DHCP périmés (aucune VM correspondante) : " ,
" en " : " Stale DHCP leases (no matching VM): " ,
} ,
" Clear these stale leases? (y/N): " : {
" fr " : " Effacer ces baux périmés ? (o/N, défaut : non) : " ,
" en " : " Clear these stale leases? (y/N, default: no): " ,
} ,
" Deploy ERPLibre infra (one minimal VM per image) " : {
" fr " : " Déployer l ' infra ERPLibre (une VM minimale par image) " ,
" en " : " Deploy ERPLibre infra (one minimal VM per image) " ,
} ,
" Deploy ERPLibre infra: one minimal VM per image " : {
" fr " : " Déployer l ' infra ERPLibre : une VM minimale par image " ,
" en " : " Deploy ERPLibre infra: one minimal VM per image " ,
} ,
" Cannot load QEMU catalog: " : {
" fr " : " Impossible de charger le catalogue QEMU : " ,
" en " : " Cannot load QEMU catalog: " ,
} ,
" Distributions: " : {
" fr " : " Distributions : " ,
" en " : " Distributions: " ,
} ,
" Whole catalog (every version) " : {
" fr " : " Tout le catalogue (chaque version) " ,
" en " : " Whole catalog (every version) " ,
} ,
" The main version of each distro (marked *) " : {
" fr " : " La version principale de chaque distro (marquée *) " ,
" en " : " The main version of each distro (marked *) " ,
} ,
" Selection (numbers, ' all ' or ' principal ' , default: all): " : {
" fr " : " Sélection (numéros, « all » ou « principal », défaut : all) : " ,
" en " : " Selection (numbers, ' all ' or ' principal ' , default: all): " ,
} ,
" Selection (numbers, or ' all ' , default: all): " : {
" fr " : " Sélection (numéros, ou « all », défaut : all) : " ,
" en " : " Selection (numbers, or ' all ' , default: all): " ,
} ,
" Nothing selected. " : {
" fr " : " Rien de sélectionné. " ,
" en " : " Nothing selected. " ,
} ,
" Deployment plan " : {
" fr " : " Plan de déploiement " ,
" en " : " Deployment plan " ,
} ,
" disk " : {
" fr " : " disque " ,
" en " : " disk " ,
} ,
" Total RAM (all running): " : {
" fr " : " RAM totale (toutes actives) : " ,
" en " : " Total RAM (all running): " ,
} ,
" Total virtual disk (thin qcow2): " : {
" fr " : " Disque virtuel total (qcow2 thin) : " ,
" en " : " Total virtual disk (thin qcow2): " ,
} ,
" Host RAM available: " : {
" fr " : " RAM disponible de l ' hôte : " ,
" en " : " Host RAM available: " ,
} ,
" Total RAM exceeds host free RAM: not all VMs will run at once. " : {
" fr " : " La RAM totale dépasse la RAM libre de l ' hôte : les VM ne "
" tourneront pas toutes en même temps. " ,
" en " : " Total RAM exceeds host free RAM: not all VMs will run at once. " ,
} ,
" Install ERPLibre into ~/git/erplibre on each VM? (Y/n): " : {
" fr " : " Installer ERPLibre dans ~/git/erplibre sur chaque VM ? "
" (O/n, défaut : oui) : " ,
" en " : " Install ERPLibre into ~/git/erplibre on each VM? "
" (Y/n, default: yes): " ,
} ,
" Deploy these VMs now? (Y/n): " : {
" fr " : " Déployer ces VM maintenant ? (O/n, défaut : oui) : " ,
" en " : " Deploy these VMs now? (Y/n, default: yes): " ,
} ,
" Discard everything and start over? (y/N): " : {
" fr " : " Tout abandonner et recommencer ? (o/N, défaut : non) : " ,
" en " : " Discard everything and start over? (y/N, default: no): " ,
} ,
" Final review before deployment " : {
" fr " : " Récapitulatif final avant déploiement " ,
" en " : " Final review before deployment " ,
} ,
" VMs to create: " : {
" fr " : " VM à créer : " ,
" en " : " VMs to create: " ,
} ,
" Existing, left untouched: " : {
" fr " : " Existantes, laissées intactes : " ,
" en " : " Existing, left untouched: " ,
} ,
[UPD] todo : nommer la case d'installation par ce qu'elle commande
« Installer ERPLibre » commandait TOUTE installation, l'hyperviseur Proxmox
VE compris : décochée, une VM Proxmox restait une Debian nue sans que rien
ne l'explique. Elle devient « Installer un logiciel dans la VM », passe sous
le type de VM — juste avant les sections qu'elle commande — et le suivi la
quitte, puisqu'il regarde la VM arriver même quand rien ne s'installe.
Ce qu'elle rend sans effet se grise, titre compris. Trois états et non deux :
sans installation mais AVEC un bureau, le magasin d'applications et les
outils de la phase « avant » servent encore ; seuls ceux qui vivent dans le
dépôt s'éteignent. Griser en bloc aurait menti autant que de tout laisser.
--- EN ---
"Install ERPLibre" commanded EVERY install, the Proxmox VE hypervisor
included: unticked, a Proxmox VM stayed a bare Debian with nothing to explain
it. It becomes "Install software in the VM", moves under the VM type — right
before the sections it commands — and the dashboard leaves it, since that
watches the VM arrive even when nothing is installed.
What it makes ineffective now greys out, title included. Three states, not
two: with no install but WITH a desktop, the application store and the
"before" tools still act; only those living in the repository go dark.
Greying the lot would have lied as much as greying nothing.
Assisted-by: Claude Opus 5
2026-08-24 02:32:09 -04:00
# Renommée : la case commande TOUTE installation — ERPLibre, Odoo, mais
# aussi l'hyperviseur Proxmox VE, qui n'installe ni l'un ni l'autre.
" Install: " : {
" fr " : " Installation : " ,
" en " : " Install: " ,
} ,
" Install software in the VM " : {
" fr " : " Installer un logiciel dans la VM " ,
" en " : " Install software in the VM " ,
} ,
" Installation " : {
" fr " : " Installation " ,
" en " : " Installation " ,
} ,
" Monitoring and parallelism " : {
" fr " : " Suivi et parallélisme " ,
" en " : " Monitoring and parallelism " ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
} ,
" profile " : {
" fr " : " profil " ,
" en " : " profile " ,
} ,
" branch " : {
" fr " : " branche " ,
" en " : " branch " ,
} ,
" production (/opt, confined) " : {
" fr " : " production (/opt, confiné) " ,
" en " : " production (/opt, confined) " ,
} ,
" development (~/git) " : {
" fr " : " développement (~/git) " ,
" en " : " development (~/git) " ,
} ,
" no " : {
" fr " : " non " ,
" en " : " no " ,
} ,
" SSH key: " : {
" fr " : " Clé SSH : " ,
" en " : " SSH key: " ,
} ,
" ~/.ssh/config: " : {
" fr " : " ~/.ssh/config : " ,
" en " : " ~/.ssh/config: " ,
} ,
" one entry per VM " : {
" fr " : " une entrée par VM " ,
" en " : " one entry per VM " ,
} ,
" untouched " : {
" fr " : " inchangé " ,
" en " : " untouched " ,
} ,
" Parallelism: " : {
" fr " : " Parallélisme : " ,
" en " : " Parallelism: " ,
} ,
" at a time " : {
" fr " : " à la fois " ,
" en " : " at a time " ,
} ,
" Nothing to create - every VM already exists. " : {
" fr " : " Rien à créer — toutes les VM existent déjà. " ,
" en " : " Nothing to create — every VM already exists. " ,
} ,
" Name collisions detected " : {
" fr " : " Collisions de noms détectées " ,
" en " : " Name collisions detected " ,
} ,
" VM already defined - SKIPPED, nothing overwritten " : {
" fr " : " VM déjà définie — IGNORÉE, rien ne sera écrasé " ,
" en " : " VM already defined — SKIPPED, nothing overwritten " ,
} ,
" disk present without VM - deployment will FAIL " : {
" fr " : " disque présent sans VM — le déploiement ÉCHOUERA " ,
" en " : " disk present without VM - deployment will FAIL " ,
} ,
" Remove it by hand, or rename the VM. " : {
" fr " : " L ' effacer à la main, ou renommer la VM. " ,
" en " : " Remove it by hand, or rename the VM. " ,
} ,
" Continue despite these collisions? (y/N): " : {
" fr " : " Continuer malgré ces collisions ? (o/N, défaut : non) : " ,
" en " : " Continue despite these collisions? (y/N, default: no): " ,
} ,
" Resolving VM IPs (parallel, emulated boot is slow)... " : {
" fr " : " Résolution des IP des VM (en parallèle, le démarrage émulé "
" est lent)... " ,
" en " : " Resolving VM IPs (parallel, emulated boot is slow)... " ,
} ,
" no IP " : {
" fr " : " pas d ' IP " ,
" en " : " no IP " ,
} ,
" Cloning ERPLibre on each VM " : {
" fr " : " Clonage d ' ERPLibre sur chaque VM " ,
" en " : " Cloning ERPLibre on each VM " ,
} ,
[REF] déploiement : la branche, le profil et le type se choisissent par VM
Sur Proxmox on déploie le plus souvent un parc MIXTE — un hyperviseur
imbriqué à côté de VM ERPLibre. C'est exactement le cas où un réglage par
machine sert, et c'est le seul écran qui ne l'offrait pas : ses rangées
n'avaient ni branche, ni profil, ni type.
Les trois choix et leur gestionnaire — quatre-vingts lignes — rejoignent le
socle. Les dupliquer aurait remis en place le mécanisme de dérive qu'on vient
d'enlever. L'écran QEMU/KVM perd encore 130 lignes sans qu'un widget, un
modèle ou une spec ne bouge : ancien et nouveau montés dans le même
processus, mêmes rangées, mêmes valeurs après avoir changé une branche, un
type et un profil.
Le déploiement suit : il lisait la seule valeur commune alors que le plan
portait déjà le choix par rangée. Une seule VM qui s'écarte suffit à rendre
la carte nécessaire — « len(set) > 1 » ne l'aurait pas vu.
Un défaut trouvé par un test, pas à l'usage : l'écho du montage se
reconnaissait à sa commande, or quand la commande imposée par le système
n'est pas dans la liste proposée, la liste retombe au rang 0 — et l'écho de
ce rang 0 effaçait l'imposition. Un Proxmox imbriqué reprenait ERPLibre et
Odoo 18. L'écho se reconnaît maintenant au RANG affiché.
--- EN ---
On Proxmox you usually deploy a MIXED fleet — a nested hypervisor next to
ERPLibre VMs. That is exactly where a per-machine setting earns its keep, and
it was the only screen without one: its rows had no branch, no profile, no
type.
The three choices and their handler — eighty lines — move into the shared
foundation. Duplicating them would have restored the very drift mechanism we
just removed. The QEMU/KVM screen loses another 130 lines with no widget,
model or spec moving: old and new mounted in one process, same rows, same
values after changing a branch, a type and a profile.
The deployment follows: it read the single common value while the plan
already carried the per-row choice. One VM that differs is enough to require
the map — "len(set) > 1" would not have seen it.
One defect found by a test, not by use: the mount echo was recognised by its
command, yet when the command imposed by the guest OS is absent from the
offered list, the list falls back to index 0 — and that index-0 echo erased
the imposition. A nested Proxmox took ERPLibre and Odoo 18 back. The echo is
now recognised by the DISPLAYED index.
Assisted-by: Claude Opus 5
2026-08-24 23:31:13 -04:00
" per VM " : {
" fr " : " par VM " ,
" en " : " per VM " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" Installing ERPLibre on each VM " : {
" fr " : " Installation d ' ERPLibre sur chaque VM " ,
" en " : " Installing ERPLibre on each VM " ,
} ,
" Install ERPLibre into ~/git/erplibre on this VM? (y/N): " : {
" fr " : " Installer ERPLibre dans ~/git/erplibre sur cette VM ? (o/N, défaut : non) : " ,
" en " : " Install ERPLibre into ~/git/erplibre on this VM? (y/N, default: no): " ,
} ,
" installing ERPLibre " : {
" fr " : " installation d ' ERPLibre " ,
" en " : " installing ERPLibre " ,
} ,
" no IP obtained, ERPLibre install skipped. " : {
" fr " : " aucune IP obtenue, installation ERPLibre ignorée. " ,
" en " : " no IP obtained, ERPLibre install skipped. " ,
} ,
" waiting for SSH... " : {
" fr " : " attente du SSH... " ,
" en " : " waiting for SSH... " ,
} ,
" SSH not reachable, ERPLibre install skipped. " : {
" fr " : " SSH injoignable, installation ERPLibre ignorée. " ,
" en " : " SSH not reachable, ERPLibre install skipped. " ,
} ,
" What to install on the VM(s)? " : {
" fr " : " Que veut-on installer sur la/les VM ? " ,
" en " : " What to install on the VM(s)? " ,
} ,
" Choice (number, blank = Odoo 18): " : {
" fr " : " Choix (numéro, vide = Odoo 18) : " ,
" en " : " Choice (number, blank = Odoo 18): " ,
} ,
" ERPLibre + all Odoo versions " : {
" fr " : " ERPLibre + toutes les versions Odoo " ,
" en " : " ERPLibre + all Odoo versions " ,
} ,
" ERPLibre only (no Odoo) " : {
" fr " : " ERPLibre seulement (sans Odoo) " ,
" en " : " ERPLibre only (no Odoo) " ,
} ,
" ERPLibre mobile (home) " : {
" fr " : " ERPLibre mobile (home) " ,
" en " : " ERPLibre mobile (home) " ,
} ,
" ERPLibre Deployment (+ QEMU + dev) " : {
" fr " : " ERPLibre Déploiement (+ QEMU + dev) " ,
" en " : " ERPLibre Deployment (+ QEMU + dev) " ,
} ,
" Interactive monitoring dashboard? (y/N): " : {
" fr " : " Suivi interactif (dashboard) ? (O/n, défaut : oui) : " ,
" en " : " Interactive monitoring dashboard? (Y/n, default: yes): " ,
} ,
" resolving IP... " : {
" fr " : " résolution de l ' IP... " ,
" en " : " resolving IP... " ,
} ,
" no IP, skipped. " : {
" fr " : " pas d ' IP, ignorée. " ,
" en " : " no IP, skipped. " ,
} ,
" No VM to install. " : {
" fr " : " Aucune VM à installer. " ,
" en " : " No VM to install. " ,
} ,
" Opening the interactive monitor... " : {
" fr " : " Ouverture du suivi interactif... " ,
" en " : " Opening the interactive monitor... " ,
} ,
" Monitor closed. Installs keep running in the background. " : {
" fr " : " Suivi fermé. Les installations continuent en arrière-plan. " ,
" en " : " Monitor closed. Installs keep running in the background. " ,
} ,
" Logs: " : {
" fr " : " Logs : " ,
" en " : " Logs: " ,
} ,
" Read the logs: " : {
" fr " : " Lire les logs : " ,
" en " : " Read the logs: " ,
} ,
" Log files: " : {
" fr " : " Fichiers de log : " ,
" en " : " Log files: " ,
} ,
" Parallel deployments (default: " : {
" fr " : " Déploiements en parallèle (défaut : " ,
" en " : " Parallel deployments (default: " ,
} ,
" VMs " : {
" fr " : " VM " ,
" en " : " VMs " ,
} ,
" Deploy summary: " : {
" fr " : " Bilan déploiement : " ,
" en " : " Deploy summary: " ,
} ,
" IPs resolved: " : {
" fr " : " IP résolues : " ,
" en " : " IPs resolved: " ,
} ,
" still waiting for " : {
" fr " : " encore en attente de " ,
" en " : " still waiting for " ,
} ,
" TOTAL summary " : {
" fr " : " Sommaire TOTAL " ,
" en " : " TOTAL summary " ,
} ,
" VMs deployed: " : {
" fr " : " VM déployées : " ,
" en " : " VMs deployed: " ,
} ,
" total incl. existing: " : {
" fr " : " total avec existantes : " ,
" en " : " total incl. existing: " ,
} ,
" Total time: " : {
" fr " : " Temps total : " ,
" en " : " Total time: " ,
} ,
" Deploying " : {
" fr " : " Déploiement de " ,
" en " : " Deploying " ,
} ,
" parallel jobs: " : {
" fr " : " tâches parallèles : " ,
" en " : " parallel jobs: " ,
} ,
" ERPLibre infra deployment done. " : {
" fr " : " Déploiement de l ' infra ERPLibre terminé. " ,
" en " : " ERPLibre infra deployment done. " ,
} ,
" Manage with: " : {
" fr " : " Gérer avec : " ,
" en " : " Manage with: " ,
} ,
" Fetching ERPLibre branch list... " : {
" fr " : " Récupération de la liste des branches ERPLibre... " ,
" en " : " Fetching ERPLibre branch list... " ,
} ,
" Branch (default: " : {
" fr " : " Branche (défaut : " ,
" en " : " Branch (default: " ,
} ,
" Branches: " : {
" fr " : " Branches : " ,
" en " : " Branches: " ,
} ,
" Choice (number or name, default: " : {
" fr " : " Choix (numéro ou nom, défaut : " ,
" en " : " Choice (number or name, default: " ,
} ,
" no IP obtained, ERPLibre clone skipped. " : {
" fr " : " aucune IP obtenue, clonage ERPLibre ignoré. " ,
" en " : " no IP obtained, ERPLibre clone skipped. " ,
} ,
" cloning ERPLibre " : {
" fr " : " clonage d ' ERPLibre " ,
" en " : " cloning ERPLibre " ,
} ,
" VM name (required): " : {
" fr " : " Nom de la VM (requis) : " ,
" en " : " VM name (required): " ,
} ,
" VM name is required! " : {
" fr " : " Le nom de la VM est requis ! " ,
" en " : " VM name is required! " ,
} ,
" VM name: " : {
" fr " : " Nom de la VM : " ,
" en " : " VM name: " ,
} ,
" VM name or ID: " : {
" fr " : " Nom ou ID de la VM : " ,
" en " : " VM name or ID: " ,
} ,
" VM name or ID (or ' all ' ): " : {
" fr " : " Nom ou ID de la VM (ou « all ») : " ,
" en " : " VM name or ID (or ' all ' ): " ,
} ,
" RAM in MB (blank = version minimum): " : {
" fr " : " RAM en Mo (vide = minimum de la version) : " ,
" en " : " RAM in MB (blank = version minimum): " ,
} ,
" vCPUs (default: 2): " : {
" fr " : " vCPU (défaut : 2) : " ,
" en " : " vCPUs (default: 2): " ,
} ,
" Disk size (blank = version min; e.g. 30G, 1T): " : {
" fr " : " Taille du disque (vide = min. version ; ex. 30G, 1T) : " ,
" en " : " Disk size (blank = version min; e.g. 30G, 1T): " ,
} ,
" VM name (default: " : {
" fr " : " Nom de la VM (défaut : " ,
" en " : " VM name (default: " ,
} ,
" Deployment failed (see the error above). " : {
" fr " : " Échec du déploiement (voir l ' erreur ci-dessus). " ,
" en " : " Deployment failed (see the error above). " ,
} ,
" SSH public key path " : {
" fr " : " Chemin de la clé publique SSH " ,
" en " : " SSH public key path " ,
} ,
[REF] déploiement : un seul socle pour ce qui décrit le système invité
Type de VM, production, magasin d'applications, outils de développement,
fuseau horaire, interpréteur Python : six réglages qui décrivent l'invité, pas
la machine qui le porte. Ils valaient donc déjà sur Proxmox — l'écran n'en
offrait que trois. Une VM créée là-bas naissait serveur nu, sans outils et en
UTC, et rien ne le disait.
La duplication était le mécanisme de la dérive : chaque correctif se posait
sur un seul des deux écrans. Les six vivent maintenant dans un socle commun,
widgets ET logique, et le contexte qui les nourrit est écrit une fois. L'écran
QEMU/KVM perd 330 lignes sans qu'un widget ni une valeur de spec ne bouge —
vérifié en montant l'ancien et le nouveau dans le même processus.
Trois conséquences se sont propagées seules : le disque annoncé (bureau et
outils compris) atteint enfin « qm resize », le parallélisme suit les cœurs de
l'hôte au lieu d'un plafond de quatre, et le nom prend le suffixe du bureau —
sans lui, une VM graphique et sa jumelle serveur se disputaient le même.
Deux défauts nommés au passage. Le fuseau : « qm set » n'en pose pas, il part
maintenant par ssh avant l'installation. Et l'architecture d'une VM Proxmox
venait de « virsh », qui ne connaît que les domaines d'ici — une VM ARM prise
pour x86_64 recevait Android Studio, que Google ne publie pas pour elle.
Le test porte sur la PARITÉ, pas sur six comportements : ajouter un réglage à
un seul écran le fait échouer. Il a d'abord échoué à se lancer — hors des
préfixes du lanceur, douze tests n'ont jamais tourné et le total n'avait pas
bougé. La règle de nommage est maintenant dans son en-tête.
--- EN ---
VM type, production, app store, development tools, timezone, Python
interpreter: six settings that describe the guest, not the machine hosting it.
They already applied to Proxmox — the screen offered three. A VM created there
was born a bare server, no tools, in UTC, and nothing said so.
Duplication was the mechanism of the drift: each fix landed on one screen
only. The six now live in a shared foundation, widgets AND logic, and the
context feeding them is written once. The QEMU/KVM screen loses 330 lines with
no widget and no spec value moving — verified by mounting the old and the new
in one process.
Three consequences followed on their own: the announced disk (desktop and
tools included) finally reaches "qm resize", parallelism follows the host's
cores instead of a cap of four, and the name takes the desktop suffix —
without it a graphical VM and its server twin fought over the same one.
Two defects named along the way. The timezone: "qm set" sets none, it now goes
over ssh before the install. And a Proxmox VM's architecture came from
"virsh", which only knows local domains — an ARM VM taken for x86_64 got
Android Studio, which Google does not publish for it.
The test covers PARITY, not six behaviours: adding a setting to one screen
alone fails it. It first failed to run at all — outside the runner's prefixes,
twelve tests never ran and the total had not moved. The naming rule is now in
its header.
Assisted-by: Claude Opus 5
2026-08-24 22:45:50 -04:00
" timezone not set " : {
" fr " : " fuseau non posé " ,
" en " : " timezone not set " ,
} ,
[IMP] qemu: a VM one can reach, and a first boot that does not stall
The monitor froze the address given at launch, so it lost the VM as soon as
cloud-init renamed the host and DHCP handed out another lease. It now
re-resolves at each attempt, in the views too, and reads virsh without sudo
— « sudo -n » fails in a detached session with no tty.
First boot also stopped paying for what it does not need: the guest agent
leaves cloud-init, snapd and locale-gen go, apt takes the fastest mirror.
The timezone follows the host. And when KVM is missing, the deployment says
so before the wait instead of being mysteriously fifteen times slower.
--- FR ---
Le suivi figeait l'adresse connue au lancement : il perdait donc la VM dès
que cloud-init posait le vrai nom d'hôte et que DHCP donnait un autre bail.
Il la ré-résout désormais à chaque tentative, dans les vues aussi, et lit
virsh sans sudo — « sudo -n » échoue dans une session détachée, sans tty.
Le premier démarrage cesse aussi de payer l'inutile : l'agent invité sort
de cloud-init, snapd et locale-gen disparaissent, apt prend le miroir le
plus rapide. Le fuseau suit l'hôte. Et faute de KVM, le déploiement le dit
avant l'attente, au lieu d'être quinze fois plus lent sans raison visible.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
" Timezone " : {
" fr " : " Fuseau horaire " ,
" en " : " Timezone " ,
} ,
" virsh cannot reach qemu:///system without sudo. " : {
" fr " : " virsh ne peut pas joindre qemu:///system sans sudo. " ,
" en " : " virsh cannot reach qemu:///system without sudo. " ,
} ,
" The install monitor runs detached and cannot type a " : {
" fr " : " Le suivi d ' installation tourne détaché et ne peut pas saisir " ,
" en " : " The install monitor runs detached and cannot type a " ,
} ,
" password: it would lose the VM when its lease moves. " : {
" fr " : " de mot de passe : il perdrait la VM au changement de bail. " ,
" en " : " password: it would lose the VM when its lease moves. " ,
} ,
" You are in the libvirt group, but this session " : {
" fr " : " Vous êtes dans le groupe libvirt, mais cette session est " ,
" en " : " You are in the libvirt group, but this session " ,
} ,
" predates it. Log out and back in, or run: " : {
" fr " : " antérieure. Reconnectez-vous, ou lancez : " ,
" en " : " predates it. Log out and back in, or run: " ,
} ,
" Group is active, so the cause is elsewhere: " : {
" fr " : " Le groupe est actif : la cause est donc ailleurs. " ,
" en " : " Group is active, so the cause is elsewhere: " ,
} ,
" Add your user to the libvirt group? " : {
" fr " : " Ajouter votre utilisateur au groupe libvirt ? " ,
" en " : " Add your user to the libvirt group? " ,
} ,
" Run it now? (Y/n): " : {
" fr " : " L ' exécuter maintenant ? (O/n) : " ,
" en " : " Run it now? (Y/n): " ,
} ,
" Command failed. " : {
" fr " : " La commande a échoué. " ,
" en " : " Command failed. " ,
} ,
" Added. Log out and back in for it to take effect, " : {
" fr " : " Ajouté. Reconnectez-vous pour que ça prenne effet, " ,
" en " : " Added. Log out and back in for it to take effect, " ,
} ,
" or start a new shell with: newgrp libvirt " : {
" fr " : " ou ouvrez un shell neuf avec : newgrp libvirt " ,
" en " : " or start a new shell with: newgrp libvirt " ,
} ,
" Timezone for the VMs " : {
" fr " : " Fuseau horaire des VM " ,
" en " : " Timezone for the VMs " ,
} ,
" Locale for the VMs " : {
" fr " : " Locale des VM " ,
" en " : " Locale for the VMs " ,
} ,
" KVM is unavailable: the VMs will be EMULATED. " : {
" fr " : " KVM indisponible : les VM seront ÉMULÉES. " ,
" en " : " KVM is unavailable: the VMs will be EMULATED. " ,
} ,
" A boot then takes 10-15 min, not under a minute. " : {
" fr " : " Un démarrage prend alors 10 à 15 min, pas moins d ' une minute. " ,
" en " : " A boot then takes 10-15 min, not under a minute. " ,
} ,
" Cause: /dev/kvm is missing. This host is itself a VM " : {
" fr " : " Cause : /dev/kvm est absent. Cet hôte est lui-même une VM " ,
" en " : " Cause: /dev/kvm is missing. This host is itself a VM " ,
} ,
" whose hypervisor does not expose nested virtualization. " : {
" fr " : " dont l ' hyperviseur n ' expose pas la virtualisation imbriquée. " ,
" en " : " whose hypervisor does not expose nested virtualization. " ,
} ,
" To fix it ON THE PARENT HYPERVISOR, not here: " : {
" fr " : " Pour corriger, SUR L ' HYPERVISEUR PARENT, pas ici : " ,
" en " : " To fix it ON THE PARENT HYPERVISOR, not here: " ,
} ,
" then set this VM to the host-passthrough CPU mode and " : {
" fr " : " puis donner à cette VM le mode CPU « host-passthrough », et " ,
" en " : " then set this VM to the host-passthrough CPU mode and " ,
} ,
" stop it and start it again - a reboot is not enough. " : {
" fr " : " l ' arrêter puis la redémarrer — un reboot ne suffit pas. " ,
" en " : " stop it and start it again - a reboot is not enough. " ,
} ,
" Without access to that hypervisor, nothing to do here. " : {
" fr " : " Sans accès à cet hyperviseur, il n ' y a rien à régler ici. " ,
" en " : " Without access to that hypervisor, nothing to do here. " ,
} ,
" Usual cause: this host is itself a VM without nested " : {
" fr " : " Cause habituelle : cet hôte est lui-même une VM sans " ,
" en " : " Usual cause: this host is itself a VM without nested " ,
} ,
" virtualization. Check: systemd-detect-virt " : {
" fr " : " virtualisation imbriquée. Vérifier : systemd-detect-virt " ,
" en " : " virtualization. Check: systemd-detect-virt " ,
} ,
" Unknown timezone, keeping " : {
" fr " : " Fuseau inconnu, on garde " ,
" en " : " Unknown timezone, keeping " ,
} ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
" none " : {
" fr " : " aucune " ,
" en " : " none " ,
} ,
" No SSH key found. Set a password instead? (Y/n): " : {
" fr " : " Aucune clé SSH trouvée. Définir un mot de passe ? (O/n) : " ,
" en " : " No SSH key found. Set a password instead? (Y/n): " ,
} ,
" Overwrite existing VM disk if present? (y/N): " : {
" fr " : " Écraser le disque de la VM s ' il existe ? (o/N, défaut : non) : " ,
" en " : " Overwrite existing VM disk if present? (y/N, default: no): " ,
} ,
" Verify SHA256 after download? (y/N): " : {
" fr " : " Vérifier le SHA256 après téléchargement ? (o/N, défaut : non) : " ,
" en " : " Verify SHA256 after download? (y/N, default: no): " ,
} ,
" QEMU - Sample dry-run (demo-vm, Ubuntu 24.04) " : {
[ADD] qemu: build the tunnel to the remote desktop
A "Remote desktop tunnel" entry under SSH configuration. It lists the
targets, resolves them, and composes the full command -- nothing to fill
in.
todo.py runs on the libvirt HOST; the tunnel starts from the workstation.
It cannot open it, but it alone knows the VM's private IP, its port and
the address through which it was reached. That last one comes from
SSH_CONNECTION, whose third field is exactly the server address used --
far safer than a "hostname" that may resolve to nothing outside.
When ~/.ssh/config already carries the entry, the tunnel takes it:
"ssh -N -L 5902:localhost:5901 <vm>". ProxyJump makes the route and
"localhost" means the VM's OWN loopback, so the tunnel survives an IP
change. That config is also the right source of targets, not the local
libvirt: a graphical VM is often nested, and virsh would only ever list
the orchestrator.
Two details: the hypervisor console needs VNC bound to the loopback, as
"listen=none" opens no socket at all; and two menu entries had no icon.
--- FR ---
Une entrée « Tunnel bureau distant » sous Configuration SSH. Elle liste
les cibles, les résout, et compose la commande complète — rien à remplir.
todo.py tourne sur l'HÔTE libvirt ; le tunnel, lui, part du poste de
travail. Il ne peut donc pas l'ouvrir, mais il est le seul à connaître
l'IP privée de la VM, son port et l'adresse par laquelle on l'a joint.
Cette dernière vient de SSH_CONNECTION, dont le troisième champ est
exactement l'adresse serveur utilisée — bien plus sûr qu'un « hostname »
qui peut ne rien résoudre depuis l'extérieur.
Quand ~/.ssh/config porte déjà l'entrée, le tunnel l'emprunte :
« ssh -N -L 5902:localhost:5901 <vm> ». Le ProxyJump fait la route et
« localhost » désigne le bouclage DE LA VM, si bien que le tunnel survit à
un changement d'IP. Ce fichier est aussi la bonne source de cibles, pas le
libvirt local : une VM graphique est souvent imbriquée, et virsh ne
listerait jamais que l'orchestrateur.
Deux détails : la console de l'hyperviseur exige un VNC sur la boucle
locale, « listen=none » n'ouvrant aucun socket ; et deux entrées de menu
n'avaient pas d'icône.
Assisted-by: Claude Opus 5
2026-08-13 03:40:22 -04:00
" fr " : " 🧰 QEMU - Exemple dry-run (demo-vm, Ubuntu 24.04) " ,
" en " : " 🧰 QEMU - Sample dry-run (demo-vm, Ubuntu 24.04) " ,
[ADD] todo: QEMU/KVM menu
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8
2026-08-07 03:22:26 -04:00
} ,
# QEMU - statistics screen
" Statistics (installs, durations, VMs) " : {
" fr " : " 📊 Statistiques (installations, durées, VM) " ,
" en " : " 📊 Statistics (installs, durations, VMs) " ,
} ,
" QEMU statistics " : { " fr " : " Statistiques QEMU " , " en " : " QEMU statistics " } ,
" No installation recorded yet. " : {
" fr " : " Aucune installation enregistrée pour l ' instant. " ,
" en " : " No installation recorded yet. " ,
} ,
" Installations " : { " fr " : " Installations " , " en " : " Installations " } ,
" Total " : { " fr " : " Total " , " en " : " Total " } ,
" succeeded " : { " fr " : " réussies " , " en " : " succeeded " } ,
" failed " : { " fr " : " échouées " , " en " : " failed " } ,
" Period " : { " fr " : " Période " , " en " : " Period " } ,
" days " : { " fr " : " jours " , " en " : " days " } ,
" Median duration " : { " fr " : " Durée médiane " , " en " : " Median duration " } ,
" min " : { " fr " : " min " , " en " : " min " } ,
" max " : { " fr " : " max " , " en " : " max " } ,
" Cumulated time " : { " fr " : " Temps cumulé " , " en " : " Cumulated time " } ,
" By distribution " : { " fr " : " Par distribution " , " en " : " By distribution " } ,
" By version " : { " fr " : " Par version " , " en " : " By version " } ,
" By architecture " : { " fr " : " Par architecture " , " en " : " By architecture " } ,
" Virtual machines " : {
" fr " : " Machines virtuelles " ,
" en " : " Virtual machines " ,
} ,
" Defined " : { " fr " : " Définies " , " en " : " Defined " } ,
" running " : { " fr " : " en cours " , " en " : " running " } ,
" stopped " : { " fr " : " arrêtées " , " en " : " stopped " } ,
" Disk used " : { " fr " : " Disque utilisé " , " en " : " Disk used " } ,
" image " : { " fr " : " image " , " en " : " image " } ,
" failure " : { " fr " : " échec " , " en " : " failure " } ,
" Reset the statistics " : {
" fr " : " Réinitialiser les statistiques " ,
" en " : " Reset the statistics " ,
} ,
" Nothing to reset. " : { " fr " : " Rien à effacer. " , " en " : " Nothing to reset. " } ,
" Erase " : { " fr " : " Effacer " , " en " : " Erase " } ,
" recorded runs " : { " fr " : " runs enregistrés " , " en " : " recorded runs " } ,
" runs erased " : { " fr " : " runs effacés " , " en " : " runs erased " } ,
" Cancelled. " : { " fr " : " Annulé. " , " en " : " Cancelled. " } ,
[ADD] migration: see and repair the website COW views
A copy-on-write view freezes the module view it came from; the module moves
on and the upgrade dies hours later on a missing anchor. These tools
predict, snapshot, diff, neutralize and reset them. The migration screen
gains the real state of each step, replay from any of them, and statistics.
--- FR ---
Une vue copy-on-write fige la vue de module dont elle vient ; le module
évolue et la mise à niveau meurt des heures plus tard sur un point
d'ancrage absent. Ces outils les prévoient, photographient, comparent,
neutralisent et réinitialisent. L'écran de migration gagne l'état réel de
chaque étape, la reprise depuis n'importe laquelle, et des statistiques.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
# Database migration - resume menu
" Migration in progress " : {
" fr " : " Migration en cours " ,
" en " : " Migration in progress " ,
} ,
" File " : { " fr " : " Fichier " , " en " : " File " } ,
" Database " : { " fr " : " Base " , " en " : " Database " } ,
" Target " : { " fr " : " Cible " , " en " : " Target " } ,
" Started " : { " fr " : " Démarrée " , " en " : " Started " } ,
" Steps " : { " fr " : " Étapes " , " en " : " Steps " } ,
" Prepare the environment " : {
" fr " : " Préparer l ' environnement " ,
" en " : " Prepare the environment " ,
} ,
" Restore and neutralize the database " : {
" fr " : " Restaurer et neutraliser la base " ,
" en " : " Restore and neutralize the database " ,
} ,
" Update all addons " : {
" fr " : " Mettre à jour tous les modules " ,
" en " : " Update all addons " ,
} ,
" Clean up before data migration " : {
" fr " : " Nettoyer avant la migration des données " ,
" en " : " Clean up before data migration " ,
} ,
" Upgrade version by version (OpenUpgrade) " : {
" fr " : " Monter de version en version (OpenUpgrade) " ,
" en " : " Upgrade version by version (OpenUpgrade) " ,
} ,
" not started " : { " fr " : " non démarrée " , " en " : " not started " } ,
" done " : { " fr " : " terminée " , " en " : " done " } ,
" partially done " : { " fr " : " partielle " , " en " : " partially done " } ,
" version bumps migrated " : {
" fr " : " montées de version faites " ,
" en " : " version bumps migrated " ,
} ,
" Continue where it stopped " : {
" fr " : " Continuer là où ça s ' est arrêté " ,
" en " : " Continue where it stopped " ,
} ,
" Replay from that step " : {
" fr " : " Reprendre à partir de cette étape " ,
" en " : " Replay from that step " ,
} ,
" erases the progression of that step and the next ones " : {
" fr " : " efface la progression de cette étape et des suivantes " ,
" en " : " erases the progression of that step and the next ones " ,
} ,
" New migration, erase everything " : {
" fr " : " Nouvelle migration, tout effacer " ,
" en " : " New migration, erase everything " ,
} ,
" Keep the zip only, ask every question again " : {
" fr " : " Garder seulement le zip, reposer toutes les questions " ,
" en " : " Keep the zip only, ask every question again " ,
} ,
" Your choice " : { " fr " : " Votre choix " , " en " : " Your choice " } ,
" Unknown choice, continuing where it stopped " : {
" fr " : " Choix inconnu, on continue là où ça s ' est arrêté " ,
" en " : " Unknown choice, continuing where it stopped " ,
} ,
" Replaying from step " : {
" fr " : " Reprise à partir de l ' étape " ,
" en " : " Replaying from step " ,
} ,
" Replay the upgrade from version N " : {
" fr " : " Reprendre la montée à partir de la version N " ,
" en " : " Replay the upgrade from version N " ,
} ,
" rebuilds the intermediate database " : {
" fr " : " recrée la base intermédiaire " ,
" en " : " rebuilds the intermediate database " ,
} ,
" Unknown version " : { " fr " : " Version inconnue " , " en " : " Unknown version " } ,
" The progression file is invalid, ignoring it " : {
" fr " : " Le fichier de progression est invalide, on l ' ignore " ,
" en " : " The progression file is invalid, ignoring it " ,
} ,
[ADD] analyse: inspect an Odoo database without restoring it
A read-only toolkit answering « what is in this database, and what will
break on a version bump »: schema size and tables no model claims,
customised views compared with their module source, and the x_ fields
Studio left behind. It reads a backup zip directly, so a 40 GB dump needs
no restore, and it reaches every answer from the TODO menu.
--- FR ---
Une boîte à outils en lecture seule qui répond à « qu'y a-t-il dans cette
base, et qu'est-ce qui cassera à la montée de version » : taille du schéma
et tables qu'aucun modèle ne réclame, vues personnalisées comparées à leur
source, champs x_ laissés par Studio. Elle lit un zip de sauvegarde tel
quel, donc sans restaurer, et tout s'atteint depuis le menu TODO.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
# --- Menu Execute › Analyse ---
# L'émoji vit dans la valeur, jamais dans la clé. Deux clés distinctes,
# comme « Data » / « Database » : l'entrée de menu porte l'émoji,
# l'étiquette du fil d'Ariane est nue — _todo_telemetry_tui traduit le
# dernier segment du chemin, et un émoji y détonnerait.
" Analyse - Odoo database analysis " : {
" fr " : " 🔬 Analyse - Analyse de base de données Odoo " ,
" en " : " 🔬 Analyse - Odoo database analysis " ,
} ,
" Analyse " : { " fr " : " Analyse " , " en " : " Analysis " } ,
[ADD] analyse: offer to install the suggested modules that are ready
After the report, only the "available" ones are offered — an unknown
module is not in the addons path and an uninstallable one has a broken
dependency, so listing them would buy three failures. How many were
left out is stated, else the count would look like a bug.
This is the only write in the Analyse menu, so its header no longer
claims otherwise. Three guards: the checkout must match the database
version (an Odoo 18 run against a 12 rewrites it before failing), both
questions default to no, and a rejected token is always shown.
--- FR ---
Après le rapport, seuls les « available » sont proposés — un module
inconnu n'est pas dans le chemin des addons, un cassé a une dépendance
morte : les lister achèterait trois échecs. Le nombre d'écartés est dit,
sinon l'écart de comptage passerait pour un bogue.
C'est la seule écriture du menu Analyse, dont l'en-tête ne prétend donc
plus le contraire. Trois garde-fous : le checkout doit être sur la
version de la base (un Odoo 18 lancé sur une 12 la réécrit avant
d'échouer), les deux questions valent non par défaut, et un jeton
refusé est toujours montré.
Assisted-by: Claude Opus 5
2026-08-21 04:31:36 -04:00
" Analyse a database. Reading never writes. " : {
" fr " : " Analyser une base ; la lecture n ' écrit jamais. " ,
" en " : " Analyse a database. Reading never writes. " ,
} ,
" About to install into " : {
" fr " : " Sur le point d ' installer dans " ,
" en " : " About to install into " ,
} ,
" Cannot install from here. " : {
" fr " : " Installation impossible d ' ici. " ,
" en " : " Cannot install from here. " ,
} ,
" Enter = cancel " : {
" fr " : " Entrée = annuler " ,
" en " : " Enter = cancel " ,
} ,
" Go ahead? " : {
" fr " : " On y va ? " ,
" en " : " Go ahead? " ,
} ,
" Ignored, not in the list: " : {
" fr " : " Ignorés, absents de la liste : " ,
" en " : " Ignored, not in the list: " ,
} ,
" Install some of the " : {
" fr " : " Installer une partie des " ,
" en " : " Install some of the " ,
} ,
" Numbers, space separated: " : {
" fr " : " Numéros, séparés par des espaces : " ,
" en " : " Numbers, space separated: " ,
} ,
" every one of them " : {
" fr " : " tous " ,
" en " : " every one of them " ,
} ,
" need repair first " : {
" fr " : " à réparer d ' abord " ,
" en " : " need repair first " ,
} ,
" suggested module(s) waiting in this database? " : {
" fr " : " module(s) suggéré(s) qui attendent dans cette base ? " ,
" en " : " suggested module(s) waiting in this database? " ,
} ,
[ADD] analyse: inspect an Odoo database without restoring it
A read-only toolkit answering « what is in this database, and what will
break on a version bump »: schema size and tables no model claims,
customised views compared with their module source, and the x_ fields
Studio left behind. It reads a backup zip directly, so a 40 GB dump needs
no restore, and it reaches every answer from the TODO menu.
--- FR ---
Une boîte à outils en lecture seule qui répond à « qu'y a-t-il dans cette
base, et qu'est-ce qui cassera à la montée de version » : taille du schéma
et tables qu'aucun modèle ne réclame, vues personnalisées comparées à leur
source, champs x_ laissés par Studio. Elle lit un zip de sauvegarde tel
quel, donc sans restaurer, et tout s'atteint depuis le menu TODO.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
" Analyse a database, without ever writing to it! " : {
" fr " : " Analyser une base, sans jamais y écrire ! " ,
" en " : " Analyse a database, without ever writing to it! " ,
} ,
# Les icônes du sous-menu Analyse. Elles sont réservées d'avance pour la
# famille entière — 🧱 index, 🎈 ballonnement, 📎 pièces jointes,
# 🔗 ir_model_data — pour qu'ajouter un outil ne demande pas d'en
# rechercher une libre, et que deux outils voisins ne se ressemblent pas.
" Structure " : { " fr " : " 🗄 Structure " , " en " : " 🗄 Structure " } ,
" Tables and database size " : {
" fr " : " 📏 Tables et poids de la base " ,
" en " : " 📏 Tables and database size " ,
} ,
" Customisation " : { " fr " : " 🎭 Personnalisation " , " en " : " 🎭 Customisation " } ,
" Customised views, website copies included " : {
" fr " : " 🖼 Vues personnalisées, copies de site web comprises " ,
" en " : " 🖼 Customised views, website copies included " ,
} ,
" Analysis failed: " : {
" fr " : " L ' analyse a échoué : " ,
" en " : " Analysis failed: " ,
} ,
" Full list and JSON output: " : {
" fr " : " Liste complète et sortie JSON : " ,
" en " : " Full list and JSON output: " ,
} ,
" A backup holds no registry, so nothing was compared with the module "
" source. The classification above needs none; only the differences do. "
" Restore it, or run this on the database. " : {
" fr " : " Une sauvegarde n ' a pas de registre, donc rien n ' a été comparé "
" avec la source du module. Le classement ci-dessus n ' en a pas besoin "
" ; seuls les écarts en ont. Restaurez-la, ou lancez ceci sur la base. " ,
" en " : " A backup holds no registry, so nothing was compared with the "
" module source. The classification above needs none; only the "
" differences do. Restore it, or run this on the database. " ,
} ,
[ADD] migration: keep the previous log when a run restarts
The progression log lives at one path and the next migration writes over it.
Restarting therefore erased everything known about the run before — which
steps passed, which modules were missing, how long each took — and that is
exactly what someone looks for after having had to restart.
Both answers that discard the progression now copy it aside first, named after
the origin database and the moment of the copy, so two attempts on the same
database do not cover each other and a file moved out of its folder still says
what it is. Partial replays keep the log, so they archive nothing.
It lands under private/, not beside the original: reinstalling wipes
.venv.erplibre/ and would take the history with it.
Two copies in the same second overwrote each other in silence — undoing the
very loss this prevents. The second one is numbered now, and the test that
had skipped that case asserts it.
Checked on the real 51 KB log from the VM: 18 states copied unchanged, the
name carries the database and the timestamp, and a log with no state at all is
not archived. 12 tests.
--- FR ---
Le journal de progression vit à un seul endroit et la migration suivante écrit
par-dessus. Recommencer effaçait donc tout ce qu'on savait de la tentative
précédente — quels paliers étaient passés, quels modules manquaient, combien
de temps chacun avait pris — soit exactement ce qu'on cherche après avoir dû
recommencer.
Les deux réponses qui jettent la progression la copient désormais d'abord,
sous un nom portant la base d'origine et l'instant de la copie, pour que deux
tentatives sur la même base ne se recouvrent pas et qu'un fichier sorti de son
dossier se décrive encore. Une reprise partielle garde le journal, donc
n'archive rien.
La copie va sous private/, pas à côté de l'originale : une réinstallation
efface .venv.erplibre/ et emporterait l'historique avec elle.
Deux copies dans la même seconde s'écrasaient en silence — annulant la perte
même qu'on évite. La seconde est numérotée, et le test qui sautait ce cas
l'affirme.
Vérifié sur le vrai journal de 51 Ko de la VM : 18 états copiés à l'identique,
le nom porte la base et l'horodatage, et un journal sans aucun état n'est pas
archivé. 12 tests.
Assisted-by: Claude Opus 5
2026-08-12 00:34:17 -04:00
" Could not archive the previous log " : {
" fr " : " Impossible d ' archiver le journal précédent " ,
" en " : " Could not archive the previous log " ,
} ,
" Previous migration log kept in " : {
" fr " : " Journal de la migration précédente conservé dans " ,
" en " : " Previous migration log kept in " ,
} ,
[FIX] migration: updating the addons early left step 2 « not started »
A database coming from an old version is updated before the neutralization —
the tool offers it at the very start. That work IS step 2, done earlier, but
it was only recorded as state_1_update_all while the screen reads state_2_*.
So the step stayed « not started » right after running.
The costly half was invisible. Step 2 skipped the work through a local
variable, which is False again on a resume: with only the old flag written,
resuming a migration re-ran update_addons_all on an already-updated database.
Hours, for nothing. The check now reads the written trace, not the session.
The screen says when it happened rather than just « done », since the moment
is exactly what raised the doubt. Logs written before this fix read correctly
without being touched — the early flag alone is recognised.
Checked on the real log from the VM: step 2 goes from « not started » to
« done early, before the neutralization », the four other steps unchanged.
10 tests; removing the fix makes the resume one fail.
--- FR ---
Une base venant d'une vieille version se met à jour avant la neutralisation —
l'outil le propose au tout début. Ce travail EST l'étape 2, faite plus tôt,
mais il n'était enregistré que sous state_1_update_all quand l'écran lit
state_2_*. L'étape restait donc « non démarrée » juste après avoir tourné.
La moitié coûteuse était invisible. L'étape 2 sautait le travail grâce à une
variable locale, remise à False à la reprise : avec seulement l'ancien drapeau
écrit, reprendre une migration relançait update_addons_all sur une base déjà à
jour. Des heures, pour rien. Le contrôle lit maintenant la trace écrite, pas
la session.
L'écran dit quand cela a eu lieu plutôt que « terminée », le moment étant
justement la source du doute. Les journaux écrits avant ce correctif se lisent
correctement sans être modifiés — le drapeau précoce seul est reconnu.
Vérifié sur le vrai journal de la VM : l'étape 2 passe de « non démarrée » à
« faite plus tôt, avant la neutralisation », les quatre autres inchangées.
10 tests ; retirer le correctif fait tomber celui de la reprise.
Assisted-by: Claude Opus 5
2026-08-12 00:43:16 -04:00
" done early, before the neutralization " : {
" fr " : " faite plus tôt, avant la neutralisation " ,
" en " : " done early, before the neutralization " ,
} ,
[IMP] migration: say when the COW question is actually asked
An early check warns, hours before the bump, that a copy will break it. It
then says « arbitrate BEFORE launching the migration » and hands over a raw
UPDATE — and no question follows. Read on a resumed run it looks like the
question already went by and was skipped, which is exactly how it was read.
It now says the migration asks at the bump itself, and shows what each copy
holds before the answer. The manual UPDATE stays, but after the two commands
that do it reversibly: telling someone to hand-write SQL when --restore exists
is offering the sharper tool first.
--- FR ---
Un contrôle précoce prévient, des heures avant le palier, qu'une copie va le
casser. Il dit ensuite « arbitrez AVANT de lancer la migration » et livre un
UPDATE brut — et aucune question ne suit. Lu au cours d'une reprise, on croit
que la question est passée et a été sautée, ce qui est exactement la lecture
qui en a été faite.
Il dit maintenant que la migration pose la question au palier lui-même, en
montrant ce que chaque copie contient avant qu'on réponde. L'UPDATE manuel
reste, mais après les deux commandes qui le font de façon réversible : dire à
quelqu'un d'écrire du SQL à la main quand --restore existe, c'est tendre
l'outil le plus coupant en premier.
Assisted-by: Claude Opus 5
2026-08-12 01:36:47 -04:00
" compare each website copy with the module view it shadows " : {
" fr " : " comparer chaque copie de site web à la vue de module qu ' elle "
" masque " ,
" en " : " compare each website copy with the module view it shadows " ,
} ,
" Compare the website copies with the view they shadow " : {
" fr " : " 🪞 Comparer les copies de site web à la vue qu ' elles masquent " ,
" en " : " 🪞 Compare the website copies with the view they shadow " ,
} ,
" website copies compared with their module view, " : {
" fr " : " copies de site web comparées à leur vue de module, " ,
" en " : " website copies compared with their module view, " ,
} ,
" differ. " : { " fr " : " diffèrent. " , " en " : " differ. " } ,
[ADD] migration: act on the COW copies when they are announced
Step 2 announced the copies the next bump will break, invited to
arbitrate, then moved on: the question came at the bump, tens of minutes
later. Yet looking writes nothing, and neutralizing here covers every bump
-- each bump database is a clone of this one.
The announcement is now a prompt: view what a copy holds, why it breaks,
full screen, or neutralize right away. Both prompts build their command in
one place, so they cannot drift apart.
Full screen means a real terminal: the viewer was started without one and
drew nothing, the pager exiting at once on a pipe.
--- FR ---
L'étape 2 annonçait les copies que le palier suivant casserait, invitait à
arbitrer, puis passait : la question venait au palier, des dizaines de
minutes plus tard. Or regarder n'écrit rien, et neutraliser ici vaut pour
tous les paliers — chaque base de palier est un clone de celle-ci.
L'annonce est désormais une invite : voir ce que porte une copie, pourquoi
elle casse, en plein écran, ou neutraliser tout de suite. Les deux invites
composent leur commande au même endroit, elles ne peuvent donc pas
diverger.
Le plein écran suppose un vrai terminal : la vue était lancée sans, et ne
dessinait rien, le pagineur sortant aussitôt sur un tube.
Assisted-by: Claude Opus 5
2026-08-13 03:12:09 -04:00
" What do you want to do with these COW copies? " : {
" fr " : " Que voulez-vous faire de ces copies COW ? " ,
" en " : " What do you want to do with these COW copies? " ,
} ,
" Enter = decide at the version bump " : {
" fr " : " Entrée = décider au palier de version " ,
" en " : " Enter = decide at the version bump " ,
} ,
" what each copy holds " : {
" fr " : " ce que chaque copie porte " ,
" en " : " what each copy holds " ,
} ,
" why it breaks " : {
" fr " : " pourquoi ça casse " ,
" en " : " why it breaks " ,
} ,
" full screen " : {
" fr " : " plein écran " ,
" en " : " full screen " ,
} ,
" neutralize now, reversible " : {
" fr " : " neutraliser maintenant, réversible " ,
" en " : " neutralize now, reversible " ,
} ,
" Undo with " : {
" fr " : " Annuler avec " ,
" en " : " Undo with " ,
} ,
" Nothing decided " : {
" fr " : " Rien de décidé " ,
" en " : " Nothing decided " ,
} ,
" the migration will ask again at the version bump. " : {
" fr " : " la migration reposera la question au palier de version. " ,
" en " : " the migration will ask again at the version bump. " ,
} ,
" Target version directory not found " : {
" fr " : " Répertoire de la version cible introuvable " ,
" en " : " Target version directory not found " ,
} ,
" No website COW view changes shape in " : {
" fr " : " Aucune vue COW de site ne change de forme dans " ,
" en " : " No website COW view changes shape in " ,
} ,
" website COW view(s) will break when moving to " : {
" fr " : " vue(s) COW de site casseront au passage à " ,
" en " : " website COW view(s) will break when moving to " ,
} ,
" the copy keeps an arch whose shape no longer matches what " : {
" fr " : " la copie garde une arch dont la forme ne correspond plus à ce que " ,
" en " : " the copy keeps an arch whose shape no longer matches what " ,
} ,
" the target module view expects. " : {
" fr " : " la vue module cible attend. " ,
" en " : " the target module view expects. " ,
} ,
" The migration offers to neutralize them, and shows what " : {
" fr " : " La migration propose de les neutraliser, et montre ce que " ,
" en " : " The migration offers to neutralize them, and shows what " ,
} ,
" each copy holds before you answer. To look now: " : {
" fr " : " chaque copie porte avant que vous répondiez. Pour regarder maintenant : " ,
" en " : " each copy holds before you answer. To look now: " ,
} ,
" reversible with --restore " : {
" fr " : " réversible avec --restore " ,
" en " : " reversible with --restore " ,
} ,
" Or by hand. To neutralize a copy, rename its key " : {
" fr " : " Ou à la main. Pour neutraliser une copie, renommer sa clé " ,
" en " : " Or by hand. To neutralize a copy, rename its key " ,
} ,
" an unmatched key is never paired with the " : {
" fr " : " une clé sans correspondance n ' est jamais appariée à la " ,
" en " : " an unmatched key is never paired with the " ,
} ,
" module view, so the copy never receives the new " : {
" fr " : " vue module, la copie ne reçoit donc jamais le nouveau " ,
" en " : " module view, so the copy never receives the new " ,
} ,
" Setting active=false alone is NOT enough: " : {
" fr " : " Mettre active=false seul ne suffit PAS : " ,
" en " : " Setting active=false alone is NOT enough: " ,
} ,
" an inactive copy keeping the same key still shadows it. " : {
" fr " : " une copie inactive gardant la même clé la masque toujours. " ,
" en " : " an inactive copy keeping the same key still shadows it. " ,
} ,
" COW view(s) belong to a module absent from " : {
" fr " : " vue(s) COW appartiennent à un module absent de " ,
" en " : " COW view(s) belong to a module absent from " ,
} ,
" COW view(s) are pages or records made in the website " : {
" fr " : " vue(s) COW sont des pages ou des enregistrements faits dans " ,
" en " : " COW view(s) are pages or records made in the website " ,
} ,
" editor (no module view of that name): not at risk. " : {
" fr " : " l ' éditeur de site (aucune vue module de ce nom) : hors de danger. " ,
" en " : " editor (no module view of that name): not at risk. " ,
} ,
" Use -v to list them. " : {
" fr " : " Utiliser -v pour les lister. " ,
" en " : " Use -v to list them. " ,
} ,
" No view with this prefix on " : {
" fr " : " Aucune vue avec ce préfixe sur " ,
" en " : " No view with this prefix on " ,
} ,
" archived COW view(s) on " : {
" fr " : " vue(s) COW archivées sur " ,
" en " : " archived COW view(s) on " ,
} ,
" Their arch is intact. Restore them all with --restore, " : {
" fr " : " Leur arch est intacte. Toutes les restaurer avec --restore, " ,
" en " : " Their arch is intact. Restore them all with --restore, " ,
} ,
" once the module view they shadow has the right shape. " : {
" fr " : " une fois que la vue module qu ' elles masquent a la bonne forme. " ,
" en " : " once the module view they shadow has the right shape. " ,
} ,
" COW view(s) restored on " : {
" fr " : " vue(s) COW restaurées sur " ,
" en " : " COW view(s) restored on " ,
} ,
" No website COW view to neutralize. " : {
" fr " : " Aucune vue COW de site à neutraliser. " ,
" en " : " No website COW view to neutralize. " ,
} ,
" website COW view(s) would break the bump " : {
" fr " : " vue(s) COW de site casseraient le palier " ,
" en " : " website COW view(s) would break the bump " ,
} ,
" to " : {
" fr " : " vers " ,
" en " : " to " ,
} ,
" Dry-run. Add --apply to rename their key to " : {
" fr " : " Simulation. Ajouter --apply pour renommer leur clé en " ,
" en " : " Dry-run. Add --apply to rename their key to " ,
} ,
" and deactivate them. Reversible " : {
" fr " : " et les désactiver. Réversible " ,
" en " : " and deactivate them. Reversible " ,
} ,
" with --restore; the arch stays in database. " : {
" fr " : " avec --restore ; l ' arch reste en base. " ,
" en " : " with --restore; the arch stays in database. " ,
} ,
" COW view(s) neutralized (key prefixed with " : {
" fr " : " vue(s) COW neutralisées (clé préfixée par " ,
" en " : " COW view(s) neutralized (key prefixed with " ,
} ,
" deactivated). The arch is kept as an " : {
" fr " : " désactivées). L ' arch est conservée comme " ,
" en " : " deactivated). The arch is kept as an " ,
} ,
" archive; use --restore to undo. " : {
" fr " : " archive ; utiliser --restore pour annuler. " ,
" en " : " archive; use --restore to undo. " ,
} ,
" target inherits, copy holds a standalone template " : {
" fr " : " la cible hérite, la copie porte un modèle autonome " ,
" en " : " target inherits, copy holds a standalone template " ,
} ,
" target is a root view, copy holds inheritance specs " : {
" fr " : " la cible est une vue racine, la copie porte des specs d ' héritage " ,
" en " : " target is a root view, copy holds inheritance specs " ,
} ,
" No module view carries this key, so there is nothing to " : {
" fr " : " Aucune vue module ne porte cette clé, il n ' y a donc rien à " ,
" en " : " No module view carries this key, so there is nothing to " ,
} ,
" compare: this copy is a page made in the website editor. " : {
" fr " : " comparer : cette copie est une page faite dans l ' éditeur de site. " ,
" en " : " compare: this copy is a page made in the website editor. " ,
} ,
" The copy is identical to the module view. " : {
" fr " : " La copie est identique à la vue module. " ,
" en " : " The copy is identical to the module view. " ,
} ,
" line(s) added " : {
" fr " : " ligne(s) ajoutée(s) " ,
" en " : " line(s) added " ,
} ,
" removed " : {
" fr " : " retirée(s) " ,
" en " : " removed " ,
} ,
" this is what neutralizing gives up. " : {
" fr " : " c ' est ce que la neutralisation abandonne. " ,
" en " : " this is what neutralizing gives up. " ,
} ,
" the module no longer declares this template " : {
" fr " : " le module ne déclare plus ce modèle " ,
" en " : " the module no longer declares this template " ,
} ,
" inheritance specs (needs inherit_id) " : {
" fr " : " des specs d ' héritage (exige inherit_id) " ,
" en " : " inheritance specs (needs inherit_id) " ,
} ,
" a standalone template " : {
" fr " : " un modèle autonome " ,
" en " : " a standalone template " ,
} ,
" Odoo changing the shape of its own template is NOT the " : {
" fr " : " Qu ' Odoo change la forme de son propre modèle n ' est PAS le " ,
" en " : " Odoo changing the shape of its own template is NOT the " ,
} ,
" problem: on a database without a copy, the module upgrade " : {
" fr " : " problème : sur une base sans copie, la mise à jour du module " ,
" en " : " problem: on a database without a copy, the module upgrade " ,
} ,
" rewrites the view and nothing breaks. It breaks here because " : {
" fr " : " réécrit la vue et rien ne casse. Ça casse ici parce qu ' une " ,
" en " : " rewrites the view and nothing breaks. It breaks here because " ,
} ,
" a COPY exists and froze the old shape — the copy follows the " : {
" fr " : " COPIE existe et a figé l ' ancienne forme — la copie suit le " ,
" en " : " a COPY exists and froze the old shape — the copy follows the " ,
} ,
" module and becomes an extension, while still holding a " : {
" fr " : " module et devient une extension, tout en portant encore un " ,
" en " : " module and becomes an extension, while still holding a " ,
} ,
" standalone template. Odoo then applies that template as an " : {
" fr " : " modèle autonome. Odoo applique alors ce modèle comme une " ,
" en " : " standalone template. Odoo then applies that template as an " ,
} ,
" inheritance spec and stops on « cannot be located in parent " : {
" fr " : " spec d ' héritage et s ' arrête sur « cannot be located in parent " ,
" en " : " inheritance spec and stops on « cannot be located in parent " ,
} ,
" view ». " : {
" fr " : " view ». " ,
" en " : " view ». " ,
} ,
" This copy has no module view of that name: it is a page " : {
" fr " : " Cette copie n ' a aucune vue module de ce nom : c ' est une page " ,
" en " : " This copy has no module view of that name: it is a page " ,
} ,
" made in the website editor, and nothing holds its content. " : {
" fr " : " faite dans l ' éditeur de site, et rien d ' autre ne porte son contenu. " ,
" en " : " made in the website editor, and nothing holds its content. " ,
} ,
" This copy is IDENTICAL to the module view it shadows: it " : {
" fr " : " Cette copie est IDENTIQUE à la vue module qu ' elle masque : elle " ,
" en " : " This copy is IDENTICAL to the module view it shadows: it " ,
} ,
" holds no customization, so neutralizing it loses nothing. " : {
" fr " : " ne porte aucune personnalisation, la neutraliser ne perd rien. " ,
" en " : " holds no customization, so neutralizing it loses nothing. " ,
} ,
" This copy differs from the module view by " : {
" fr " : " Cette copie diffère de la vue module de " ,
" en " : " This copy differs from the module view by " ,
} ,
" line(s): " : {
" fr " : " ligne(s) : " ,
" en " : " line(s): " ,
} ,
" that is the customization, and all neutralizing gives up. " : {
" fr " : " c ' est la personnalisation, et tout ce que la neutralisation abandonne. " ,
" en " : " that is the customization, and all neutralizing gives up. " ,
} ,
" Run without --shape to read it. " : {
" fr " : " Lancer sans --shape pour la lire. " ,
" en " : " Run without --shape to read it. " ,
} ,
" No website COW view is at risk. " : {
" fr " : " Aucune vue COW de site n ' est en danger. " ,
" en " : " No website COW view is at risk. " ,
} ,
" No COW copy has drifted from its module view. " : {
" fr " : " Aucune copie COW n ' a dérivé de sa vue module. " ,
" en " : " No COW copy has drifted from its module view. " ,
} ,
" COW copy(ies) drifted from their module " : {
" fr " : " copie(s) COW ont dérivé de leur vue " ,
" en " : " COW copy(ies) drifted from their module " ,
} ,
" view in " : {
" fr " : " module dans " ,
" en " : " view in " ,
} ,
" Odoo surfaces this when the module view is rewritten (a " : {
" fr " : " Odoo le fait apparaître quand la vue module est réécrite (un " ,
" en " : " Odoo surfaces this when the module view is rewritten (a " ,
} ,
" version bump) or when the page is rendered. " : {
" fr " : " palier de version) ou quand la page est rendue. " ,
" en " : " version bump) or when the page is rendered. " ,
} ,
" NO module view with this key " : {
" fr " : " AUCUNE vue module avec cette clé " ,
" en " : " NO module view with this key " ,
} ,
" child " : {
" fr " : " enfant " ,
" en " : " child " ,
} ,
" cannot apply " : {
" fr " : " ne peut s ' appliquer " ,
" en " : " cannot apply " ,
} ,
" Nothing changed. Re-run with --reset <key> --apply to " : {
" fr " : " Rien n ' a changé. Relancer avec --reset <clé> --apply pour " ,
" en " : " Nothing changed. Re-run with --reset <key> --apply to " ,
} ,
" reset a copy onto its module view. " : {
" fr " : " réinitialiser une copie sur sa vue module. " ,
" en " : " reset a copy onto its module view. " ,
} ,
" no module view to reset onto, skipped. " : {
" fr " : " aucune vue module sur laquelle réinitialiser, ignorée. " ,
" en " : " no module view to reset onto, skipped. " ,
} ,
" dry-run " : {
" fr " : " simulation " ,
" en " : " dry-run " ,
} ,
" would reset " : {
" fr " : " réinitialiserait " ,
" en " : " would reset " ,
} ,
" onto " : {
" fr " : " sur " ,
" en " : " onto " ,
} ,
" reset " : {
" fr " : " réinitialisé " ,
" en " : " reset " ,
} ,
" previous arch saved to " : {
" fr " : " arch précédente sauvegardée dans " ,
" en " : " previous arch saved to " ,
} ,
" copy(ies) reset. Re-apply any real customisation " : {
" fr " : " copie(s) réinitialisée(s). Réappliquer toute vraie personnalisation " ,
" en " : " copy(ies) reset. Re-apply any real customisation " ,
} ,
" as an INHERITING view, not a copy, so it cannot go stale " : {
" fr " : " comme une vue HÉRITANTE, pas une copie, pour qu ' elle ne périme plus " ,
" en " : " as an INHERITING view, not a copy, so it cannot go stale " ,
} ,
" again. " : {
" fr " : " à l ' avenir. " ,
" en " : " again. " ,
} ,
" lxml is required to resolve the xpath expressions. " : {
" fr " : " lxml est requis pour résoudre les expressions xpath. " ,
" en " : " lxml is required to resolve the xpath expressions. " ,
} ,
" COW view(s) recorded in " : {
" fr " : " vue(s) COW enregistrées dans " ,
" en " : " COW view(s) recorded in " ,
} ,
" COW view(s) disappeared " : {
" fr " : " vue(s) COW ont disparu " ,
" en " : " COW view(s) disappeared " ,
} ,
" COW view(s) appeared " : {
" fr " : " vue(s) COW sont apparues " ,
" en " : " COW view(s) appeared " ,
} ,
" arch rewritten " : {
" fr " : " arch réécrite " ,
" en " : " arch rewritten " ,
} ,
" COW view(s) changed " : {
" fr " : " vue(s) COW ont changé " ,
" en " : " COW view(s) changed " ,
} ,
" No change on the website COW views. " : {
" fr " : " Aucun changement sur les vues COW de site. " ,
" en " : " No change on the website COW views. " ,
} ,
" Welcome to the Odoo database upgrade with ERPLibre " : {
" fr " : " Bienvenue dans la mise à niveau de base de données Odoo avec ERPLibre " ,
" en " : " Welcome to the Odoo database upgrade with ERPLibre " ,
} ,
" Select the zip file of your database backup. " : {
" fr " : " Sélectionnez le fichier zip de votre sauvegarde de base de données. " ,
" en " : " Select the zip file of your database backup. " ,
} ,
" Give the path of the file, or empty to use a file " : {
" fr " : " Donnez le chemin du fichier, ou rien pour utiliser un " ,
" en " : " Give the path of the file, or empty to use a file " ,
} ,
" browser, or type " : {
" fr " : " navigateur de fichiers, ou tapez " ,
" en " : " browser, or type " ,
} ,
" to download from production " : {
" fr " : " pour télécharger depuis la production " ,
" en " : " to download from production " ,
} ,
" Cannot retrieve the database from remote, please retry the migration. " : {
" fr " : " Impossible de récupérer la base distante, veuillez relancer la migration. " ,
" en " : " Cannot retrieve the database from remote, please retry the migration. " ,
} ,
" Open file " : {
" fr " : " Ouverture du fichier " ,
" en " : " Open file " ,
} ,
" Detected Odoo CE version " : {
" fr " : " Version Odoo CE détectée " ,
" en " : " Detected Odoo CE version " ,
} ,
" Which version do you want to upgrade to? " : {
" fr " : " Vers quelle version voulez-vous migrer ? " ,
" en " : " Which version do you want to upgrade to? " ,
} ,
" Search the Odoo version " : {
" fr " : " Recherche de la version Odoo " ,
" en " : " Search the Odoo version " ,
} ,
" Find the right environment, read the .zip file " : {
" fr " : " Recherche du bon environnement, lecture du fichier .zip " ,
" en " : " Find the right environment, read the .zip file " ,
} ,
" Would you like to install " : {
" fr " : " Voulez-vous installer " ,
" en " : " Would you like to install " ,
} ,
" You need an installed system before " : {
" fr " : " Il faut un système installé avant de " ,
" en " : " You need an installed system before " ,
} ,
" continuing, check your Odoo installation. " : {
" fr " : " continuer, vérifiez votre installation Odoo. " ,
" en " : " continuing, check your Odoo installation. " ,
} ,
" Install the environment if missing " : {
" fr " : " Installation de l ' environnement s ' il manque " ,
" en " : " Install the environment if missing " ,
} ,
" Search missing modules " : {
" fr " : " Recherche des modules manquants " ,
" en " : " Search missing modules " ,
} ,
" Install the missing modules, search for them or " : {
" fr " : " Installer les modules manquants, les chercher ou " ,
" en " : " Install the missing modules, search for them or " ,
} ,
" ask to uninstall them (can break data) " : {
" fr " : " demander à les désinstaller (peut casser des données) " ,
" en " : " ask to uninstall them (can break data) " ,
} ,
" Cannot set up the environment to begin. " : {
" fr " : " Impossible de préparer l ' environnement pour commencer. " ,
" en " : " Cannot set up the environment to begin. " ,
} ,
" Missing module " : {
" fr " : " Module manquant " ,
" en " : " Missing module " ,
} ,
" Duplicate module " : {
" fr " : " Module en double " ,
" en " : " Duplicate module " ,
} ,
" Missing or duplicate module detected at init, " : {
" fr " : " Module manquant ou en double détecté à l ' init, " ,
" en " : " Missing or duplicate module detected at init, " ,
} ,
" do you want to continue? " : {
" fr " : " voulez-vous continuer ? " ,
" en " : " do you want to continue? " ,
} ,
" Missing or duplicate module detected at init, do " : {
" fr " : " Module manquant ou en double détecté à l ' init, " ,
" en " : " Missing or duplicate module detected at init, do " ,
} ,
" you want to continue? " : {
" fr " : " voulez-vous continuer ? " ,
" en " : " you want to continue? " ,
} ,
" Which database name do you want to work with? " : {
" fr " : " Avec quel nom de base voulez-vous travailler ? " ,
" en " : " Which database name do you want to work with? " ,
} ,
" Ignore the database neutralization " : {
" fr " : " Ignorer la neutralisation de la base " ,
" en " : " Ignore the database neutralization " ,
} ,
" Working with database " : {
" fr " : " Travail sur la base " ,
" en " : " Working with database " ,
} ,
" will copy " : {
" fr " : " va copier " ,
" en " : " will copy " ,
} ,
" a file already exists, do you want to " : {
" fr " : " un fichier existe déjà, voulez-vous " ,
" en " : " a file already exists, do you want to " ,
} ,
" continue? " : {
" fr " : " continuer ? " ,
" en " : " continue? " ,
} ,
" Restore the database " : {
" fr " : " Restauration de la base " ,
" en " : " Restore the database " ,
} ,
" Update all addons before neutralizing (already " : {
" fr " : " Mettre à jour tous les modules avant la neutralisation (déjà " ,
" en " : " Update all addons before neutralizing (already " ,
} ,
" neutralized by Odoo if supported) " : {
" fr " : " neutralisée par Odoo si pris en charge) " ,
" en " : " neutralized by Odoo if supported) " ,
} ,
" Do you need to upgrade before neutralizing the " : {
" fr " : " Faut-il mettre à jour avant de neutraliser la " ,
" en " : " Do you need to upgrade before neutralizing the " ,
} ,
" database? Press enter to ignore " : {
" fr " : " base ? Entrée pour ignorer " ,
" en " : " database? Press enter to ignore " ,
} ,
" Update the database before neutralizing, by module " : {
" fr " : " Mise à jour de la base avant neutralisation, par module " ,
" en " : " Update the database before neutralizing, by module " ,
} ,
" Neutralize the database " : {
" fr " : " Neutralisation de la base " ,
" en " : " Neutralize the database " ,
} ,
" Modules to uninstall before the migration " : {
" fr " : " Modules à désinstaller avant la migration " ,
" en " : " Modules to uninstall before the migration " ,
} ,
" Uninstall modules " : {
" fr " : " Désinstallation des modules " ,
" en " : " Uninstall modules " ,
} ,
" Install modules " : {
" fr " : " Installation des modules " ,
" en " : " Install modules " ,
} ,
" Go to Settings / Technical / Cleanup... / Purge and " : {
" fr " : " Aller dans Configuration / Technique / Nettoyage... / Purger et " ,
" en " : " Go to Settings / Technical / Cleanup... / Purge and " ,
} ,
" purge the obsolete modules " : {
" fr " : " purger les modules obsolètes " ,
" en " : " purge the obsolete modules " ,
} ,
" Press enter to continue step 3 " : {
" fr " : " Entrée pour continuer l ' étape 3 " ,
" en " : " Press enter to continue step 3 " ,
} ,
" Inspect zip " : {
" fr " : " Inspection du zip " ,
" en " : " Inspect zip " ,
} ,
" Import database from zip " : {
" fr " : " Import de la base depuis le zip " ,
" en " : " Import database from zip " ,
} ,
" Succeed update all addons " : {
" fr " : " Mise à jour de tous les modules " ,
" en " : " Succeed update all addons " ,
} ,
" Clean up database before data migration " : {
" fr " : " Nettoyage de la base avant la migration des données " ,
" en " : " Clean up database before data migration " ,
} ,
" Upgrade version with OpenUpgrade " : {
" fr " : " Montée de version avec OpenUpgrade " ,
" en " : " Upgrade version with OpenUpgrade " ,
} ,
" Cleaning up database after upgrade " : {
" fr " : " Nettoyage de la base après la montée de version " ,
" en " : " Cleaning up database after upgrade " ,
} ,
" Migration finished " : {
" fr " : " Migration terminée " ,
" en " : " Migration finished " ,
} ,
" Clone to Odoo " : {
" fr " : " Clonage vers Odoo " ,
" en " : " Clone to Odoo " ,
} ,
" FAILED (status " : {
" fr " : " ÉCHEC (statut " ,
" en " : " FAILED (status " ,
} ,
" Stopping: " : {
" fr " : " Arrêt : " ,
" en " : " Stopping: " ,
} ,
" is not usable. " : {
" fr " : " n ' est pas utilisable. " ,
" en " : " is not usable. " ,
} ,
" Clone Odoo " : {
" fr " : " Clonage Odoo " ,
" en " : " Clone Odoo " ,
} ,
" nothing " : {
" fr " : " rien " ,
" en " : " nothing " ,
} ,
" Switch Odoo " : {
" fr " : " Bascule Odoo " ,
" en " : " Switch Odoo " ,
} ,
" done with update " : {
" fr " : " terminée avec mise à jour " ,
" en " : " done with update " ,
} ,
" Module upgrade Odoo " : {
" fr " : " Mise à jour des modules Odoo " ,
" en " : " Module upgrade Odoo " ,
} ,
" Database upgrade Odoo " : {
" fr " : " Migration de la base Odoo " ,
" en " : " Database upgrade Odoo " ,
} ,
" Modules to uninstall before Odoo " : {
" fr " : " Modules à désinstaller avant Odoo " ,
" en " : " Modules to uninstall before Odoo " ,
} ,
" to redo the command " : {
" fr " : " pour refaire la commande " ,
" en " : " to redo the command " ,
} ,
" Error detected, press enter to continue or " : {
" fr " : " Erreur détectée, Entrée pour continuer ou " ,
" en " : " Error detected, press enter to continue or " ,
} ,
" to stop " : {
" fr " : " pour arrêter " ,
" en " : " to stop " ,
} ,
" Clone done for Odoo " : {
" fr " : " Clonage terminé pour Odoo " ,
" en " : " Clone done for Odoo " ,
} ,
" Clone already done for Odoo " : {
" fr " : " Clonage déjà fait pour Odoo " ,
" en " : " Clone already done for Odoo " ,
} ,
" Switch already done for Odoo " : {
" fr " : " Bascule déjà faite pour Odoo " ,
" en " : " Switch already done for Odoo " ,
} ,
" Switch done with update for Odoo " : {
" fr " : " Bascule terminée avec mise à jour pour Odoo " ,
" en " : " Switch done with update for Odoo " ,
} ,
" Module upgrade done for Odoo " : {
" fr " : " Mise à jour des modules terminée pour Odoo " ,
" en " : " Module upgrade done for Odoo " ,
} ,
" Module upgrade already done for Odoo " : {
" fr " : " Mise à jour des modules déjà faite pour Odoo " ,
" en " : " Module upgrade already done for Odoo " ,
} ,
" Database upgrade done for Odoo " : {
" fr " : " Migration de la base terminée pour Odoo " ,
" en " : " Database upgrade done for Odoo " ,
} ,
" Database upgrade already done for Odoo " : {
" fr " : " Migration de la base déjà faite pour Odoo " ,
" en " : " Database upgrade already done for Odoo " ,
} ,
" Cloning to Odoo " : {
" fr " : " Clonage vers Odoo " ,
" en " : " Cloning to Odoo " ,
} ,
" from " : {
" fr " : " depuis " ,
" en " : " from " ,
} ,
" Duplicate module in Odoo " : {
" fr " : " Module en double dans Odoo " ,
" en " : " Duplicate module in Odoo " ,
} ,
" Duplicate module error detected, handle it " : {
" fr " : " Erreur de module en double détectée, la traiter " ,
" en " : " Duplicate module error detected, handle it " ,
} ,
" manually then press enter to continue. " : {
" fr " : " manuellement puis Entrée pour continuer. " ,
" en " : " manually then press enter to continue. " ,
} ,
" Missing module error detected, missing in " : {
" fr " : " Erreur de module manquant détectée, manquant dans " ,
" en " : " Missing module error detected, missing in " ,
} ,
" All of the list above " : {
" fr " : " Toute la liste ci-dessus " ,
" en " : " All of the list above " ,
} ,
" Add an extra custom one " : {
" fr " : " Ajouter un module personnalisé " ,
" en " : " Add an extra custom one " ,
} ,
" List the missing modules to delete, " : {
" fr " : " Énumérer les modules manquants à supprimer, " ,
" en " : " List the missing modules to delete, " ,
} ,
" separated by commas. The others will be " : {
" fr " : " séparés par des virgules. Les autres seront " ,
" en " : " separated by commas. The others will be " ,
} ,
" migrated " : {
" fr " : " migrés " ,
" en " : " migrated " ,
} ,
" Migration fix for Odoo " : {
" fr " : " Correctif de migration pour Odoo " ,
" en " : " Migration fix for Odoo " ,
} ,
" Migration fix done for Odoo " : {
" fr " : " Correctif de migration terminé pour Odoo " ,
" en " : " Migration fix done for Odoo " ,
} ,
" No migration fix to run for Odoo " : {
" fr " : " Aucun correctif de migration à exécuter pour Odoo " ,
" en " : " No migration fix to run for Odoo " ,
} ,
" Migration fix already done for Odoo " : {
" fr " : " Correctif de migration déjà fait pour Odoo " ,
" en " : " Migration fix already done for Odoo " ,
} ,
" List the module names to delete, " : {
" fr " : " Énumérer les noms de modules à supprimer, " ,
" en " : " List the module names to delete, " ,
} ,
" separated by commas " : {
" fr " : " séparés par des virgules " ,
" en " : " separated by commas " ,
} ,
" Missing module error " : {
" fr " : " Erreur de module manquant " ,
" en " : " Missing module error " ,
} ,
" Duplicate module error " : {
" fr " : " Erreur de module en double " ,
" en " : " Duplicate module error " ,
} ,
" Module error " : {
" fr " : " Erreur de module " ,
" en " : " Module error " ,
} ,
" Please validate the commits after the code " : {
" fr " : " Veuillez valider les commits après la migration " ,
" en " : " Please validate the commits after the code " ,
} ,
" migration. " : {
" fr " : " du code. " ,
" en " : " migration. " ,
} ,
" To show the repo status " : {
" fr " : " Pour afficher l ' état des dépôts " ,
" en " : " To show the repo status " ,
} ,
" Please validate this path in config.conf " : {
" fr " : " Veuillez valider ce chemin dans config.conf " ,
" en " : " Please validate this path in config.conf " ,
} ,
" Database migration to Odoo " : {
" fr " : " Migration de la base vers Odoo " ,
" en " : " Database migration to Odoo " ,
} ,
" is now half migrated: replaying the command on " : {
" fr " : " est maintenant à moitié migrée : rejouer la commande dessus " ,
" en " : " is now half migrated: replaying the command on " ,
} ,
" it would never recover, so it is NOT offered. " : {
" fr " : " ne s ' en remettrait jamais, ce n ' est donc PAS proposé. " ,
" en " : " it would never recover, so it is NOT offered. " ,
} ,
" The clone step has been reset. Fix the " : {
" fr " : " L ' étape de clonage a été réinitialisée. Corrigez la " ,
" en " : " The clone step has been reset. Fix the " ,
} ,
" cause, then relaunch the migration and answer " : {
" fr " : " cause, puis relancez la migration et répondez " ,
" en " : " cause, then relaunch the migration and answer " ,
} ,
" continue " : {
" fr " : " continuer " ,
" en " : " continue " ,
} ,
" will be dropped and rebuilt from the previous " : {
" fr " : " sera supprimée et reconstruite depuis la version " ,
" en " : " will be dropped and rebuilt from the previous " ,
} ,
" version before retrying. " : {
" fr " : " précédente avant de réessayer. " ,
" en " : " version before retrying. " ,
} ,
" Do you want to upgrade all " : {
" fr " : " Voulez-vous tout mettre à jour " ,
" en " : " Do you want to upgrade all " ,
} ,
" Press y/Y to upgrade all addons of the " : {
" fr " : " y/Y pour mettre à jour tous les modules de la " ,
" en " : " Press y/Y to upgrade all addons of the " ,
} ,
" Open the server with Selenium " : {
" fr " : " Ouvrir le serveur avec Selenium " ,
" en " : " Open the server with Selenium " ,
} ,
" Do you want to test this upgrade? Choose " : {
" fr " : " Voulez-vous tester cette migration ? Choisissez " ,
" en " : " Do you want to test this upgrade? Choose " ,
} ,
" or press enter to ignore it " : {
" fr " : " ou Entrée pour ignorer " ,
" en " : " or press enter to ignore it " ,
} ,
" Press enter to continue " : {
" fr " : " Entrée pour continuer " ,
" en " : " Press enter to continue " ,
} ,
" Re-update i18n, purge the data and the tables " : {
" fr " : " Remise à jour de l ' i18n, purge des données et des tables " ,
" en " : " Re-update i18n, purge the data and the tables " ,
} ,
" except mail_test and mail_test_full " : {
" fr " : " sauf mail_test et mail_test_full " ,
" en " : " except mail_test and mail_test_full " ,
} ,
" A backup can be created " : {
" fr " : " Une sauvegarde peut être créée " ,
" en " : " A backup can be created " ,
} ,
" Press y/Y or type filename.zip to export, or " : {
" fr " : " y/Y ou un nom fichier.zip pour exporter, ou " ,
" en " : " Press y/Y or type filename.zip to export, or " ,
} ,
" enter to continue " : {
" fr " : " Entrée pour continuer " ,
" en " : " enter to continue " ,
} ,
" Test the migration, press y/Y " : {
" fr " : " Tester la migration, y/Y " ,
" en " : " Test the migration, press y/Y " ,
} ,
" Documentation for this version " : {
" fr " : " Documentation de cette version " ,
" en " : " Documentation for this version " ,
} ,
" Command not found " : {
" fr " : " Commande non trouvée " ,
" en " : " Command not found " ,
} ,
" Execute command " : {
" fr " : " Exécution de la commande " ,
" en " : " Execute command " ,
} ,
2026-08-13 04:21:24 -04:00
" Textual is missing from this interpreter: " : {
" fr " : " Textual manque à cet interpréteur : " ,
" en " : " Textual is missing from this interpreter: " ,
} ,
" Run it with " : {
" fr " : " Le lancer avec " ,
" en " : " Run it with " ,
} ,
[ADD] addons: uninstall a theme the way Odoo removes one
There was no counterpart to install_addons_theme.sh, so themes were
removed with a plain --uninstall. That takes out the module, not the
theme: it skips _theme_remove(), whose first act is
_reset_default_config() — the call that writes font-number and its
three neighbours into user_values.scss.
Measured on a real 12 -> 13 migration: web.assets_frontend stopped on
« Undefined variable: $o-theme-font-number ». Odoo 12 defined it in
option_font_body_*, dropped in 13.0; only the theme still redefined
it, so removing the theme exposed a frozen 2020 customization.
theme_leftover.py then reports what unloading does not take — 15
attachments on the measured database. It deletes nothing: their
content may be the only trace of a customization.
--- FR ---
[ADD] addons : désinstaller un thème comme Odoo le retire
install_addons_theme.sh n'avait pas de symétrique : on retirait donc
les thèmes par un --uninstall nu. Cela enlève le module, pas le thème
— cela saute _theme_remove(), dont le premier geste est
_reset_default_config(), l'appel qui écrit font-number et ses trois
voisines dans user_values.scss.
Mesuré sur une vraie migration 12 → 13 : web.assets_frontend s'arrête
sur « Undefined variable: $o-theme-font-number ». Odoo 12 la
définissait dans option_font_body_*, supprimés en 13.0 ; seul le thème
la redéfinissait, et le retirer a mis à nu un SCSS figé en 2020.
theme_leftover.py signale ensuite ce que le déchargement ne prend pas
— 15 pièces jointes sur la base mesurée. Il ne supprime rien : leur
contenu peut être la seule trace d'une personnalisation.
Assisted-by: Claude Opus 5
(cherry picked from commit 16a7b1e1333752e2ef603d66f47374a22590d665)
2026-08-13 05:56:06 -04:00
" No leftover for theme " : {
" fr " : " Aucun reste pour le thème " ,
" en " : " No leftover for theme " ,
} ,
" attachment(s) still under " : {
" fr " : " pièce(s) jointe(s) encore sous " ,
" en " : " attachment(s) still under " ,
} ,
" view(s) whose key still names it " : {
" fr " : " vue(s) dont la clé le nomme encore " ,
" en " : " view(s) whose key still names it " ,
} ,
" Nothing was deleted: their content may be the only trace " : {
" fr " : " Rien n ' a été supprimé : leur contenu peut être la seule trace " ,
" en " : " Nothing was deleted: their content may be the only trace " ,
} ,
" of a customization. Read before removing. " : {
" fr " : " d ' une personnalisation. Lire avant de retirer. " ,
" en " : " of a customization. Read before removing. " ,
} ,
[ADD] migration: offer to remove the themes before the first bump
A theme carries view copies and SCSS through every version bump, and a
bump can rename what they rely on. Removing it first drops a whole
family of failures, and it can be put back afterwards.
Asked in step 1, before OpenUpgrade runs, and only when a theme is
actually installed. Default is no: removing a theme changes how a site
looks, and a migration does not decide that for its owner.
theme_default is not offered — it IS the absence of a theme.
--- FR ---
[ADD] migration : proposer de retirer les thèmes avant le premier palier
Un thème traîne des copies de vues et des SCSS à travers chaque palier
de version, et un palier peut renommer ce dont ils dépendent. Le
retirer d'abord enlève une famille entière de pannes, et se refait
ensuite.
Posée à l'étape 1, avant OpenUpgrade, et seulement s'il y a vraiment un
thème installé. Par défaut non : retirer un thème change l'apparence
d'un site, et ce n'est pas à une migration de trancher cela à la place
de son propriétaire. theme_default n'est pas proposé — il EST
l'absence de thème.
Assisted-by: Claude Opus 5
(cherry picked from commit dcbee87a6b920408c315f7354a096b0ecfabcf06)
2026-08-15 02:36:28 -04:00
" Installed theme(s) on " : {
" fr " : " Thème(s) installé(s) sur " ,
" en " : " Installed theme(s) on " ,
} ,
" A theme carries view copies and SCSS through every " : {
" fr " : " Un thème traîne des copies de vues et des SCSS à travers chaque " ,
" en " : " A theme carries view copies and SCSS through every " ,
} ,
" version bump, and a bump can rename what they rely on. " : {
" fr " : " palier de version, et un palier peut renommer ce dont ils dépendent. " ,
" en " : " version bump, and a bump can rename what they rely on. " ,
} ,
" Uninstall them properly before migrating? " : {
" fr " : " Les désinstaller proprement avant de migrer ? " ,
" en " : " Uninstall them properly before migrating? " ,
} ,
" Kept. Nothing was uninstalled. " : {
" fr " : " Conservés. Rien n ' a été désinstallé. " ,
" en " : " Kept. Nothing was uninstalled. " ,
} ,
[ADD] migration: predict which customized SCSS the next bump breaks
Customized SCSS lives in ir_attachment, frozen the day it is written,
still using the variables of that version. A bump can rename them:
website declared $o-theme-font-number in 12.0 and replaced the whole
mechanism in 13.0, so a 2020 customization stopped the frontend
bundle. Same shape as a stale COW view.
Reading the stored SCSS against the target sources answers before the
bump and names the attachment. Booting Odoo and opening a page answers
after, with « Style error » and no name — on a page a broken frontend
bundle is exactly what keeps you from reaching.
Measured: on the pre-bump database, targeting 13.0, it predicts the
failure; against its own 12.0 sources it reports nothing. That noise
floor is what makes it usable — the first version cried wolf on six
mixin parameters and named @include arguments.
--- FR ---
[ADD] migration : prédire quel SCSS personnalisé le palier va casser
Un SCSS personnalisé vit dans ir_attachment, figé le jour où il est
écrit, employant encore les variables de cette version-là. Un palier
peut les renommer : website déclarait $o-theme-font-number en 12.0 et a
remplacé le mécanisme en 13.0, arrêtant le bundle sur une
personnalisation de 2020. Même forme qu'une vue COW périmée.
Lire le SCSS stocké contre les sources cibles répond AVANT le palier et
nomme la pièce jointe. Démarrer Odoo et ouvrir une page répond après,
par « Style error » sans nom — et un bundle cassé est justement ce qui
empêche d'atteindre la page.
Mesuré : sur la base d'avant le palier, cible 13.0, il prédit la
panne ; contre ses propres sources 12.0 il ne signale rien. Ce bruit de
fond nul est ce qui le rend utilisable — la première version criait à
tort sur six paramètres de mixin et arguments nommés.
Assisted-by: Claude Opus 5
(cherry picked from commit c6611ee1431cfee94e321536d20052b8cca3ec10)
2026-08-15 03:21:25 -04:00
" No customized SCSS is at risk in " : {
" fr " : " Aucun SCSS personnalisé n ' est en danger dans " ,
" en " : " No customized SCSS is at risk in " ,
} ,
" customized SCSS use(s) a variable that " : {
" fr " : " SCSS personnalisé(s) utilisent une variable que " ,
" en " : " customized SCSS use(s) a variable that " ,
} ,
" no longer defines: the bundle will not compile. " : {
" fr " : " ne définit plus : le bundle ne compilera pas. " ,
" en " : " no longer defines: the bundle will not compile. " ,
} ,
" Each one is a copy frozen on an older version. Dropping it " : {
" fr " : " Chacun est une copie figée sur une version antérieure. L ' abandonner " ,
" en " : " Each one is a copy frozen on an older version. Dropping it " ,
} ,
" restores the module file: " : {
" fr " : " rend le fichier du module : " ,
" en " : " restores the module file: " ,
} ,
" Read it first: what it holds beyond the stale variable is " : {
" fr " : " Le lire d ' abord : ce qu ' il porte au-delà de la variable périmée est " ,
" en " : " Read it first: what it holds beyond the stale variable is " ,
} ,
" a real customization, to re-apply as a small file. " : {
" fr " : " une vraie personnalisation, à réappliquer en petit fichier. " ,
" en " : " a real customization, to re-apply as a small file. " ,
} ,
" Read the customizations above before continuing. " : {
" fr " : " Lire les personnalisations ci-dessus avant de continuer. " ,
" en " : " Read the customizations above before continuing. " ,
} ,
[ADD] migration: read the stale SCSS, then fix it, without leaving the tool
The report named the attachment and printed a command to paste. Deciding
« reset it » still meant accepting to lose one did not know what: the
diff against the module file is the only thing resetting gives up.
The tool now shows it and asks. --diff prints it, --tui browses it,
--apply resets without asking; with no flag and a terminal it asks, and
looking does not answer — the prompt comes back after each read.
--apply writes the copies under private/ first: reset_asset deletes the
attachment, so without that the customized lines would be nowhere.
The migration runs it on the real terminal, or none of this would be
reachable from there — the same pipe that was closing the TUI.
--- FR ---
[ADD] migration : lire le SCSS périmé, puis le corriger, sans sortir
Le rapport nommait la pièce jointe et imprimait une commande à coller.
Répondre « réinitialise » revenait encore à accepter de perdre on ne
sait quoi : l'écart avec le fichier du module est la seule chose que la
réinitialisation abandonne.
L'outil le montre et pose la question. --diff l'imprime, --tui le
parcourt, --apply réinitialise sans demander ; sans drapeau et devant
un terminal il demande, et regarder ne répond pas — l'invite revient
après chaque lecture. --apply écrit d'abord les copies sous private/ :
reset_asset supprime la pièce jointe, sans quoi les lignes
personnalisées ne seraient plus nulle part.
La migration le lance sur le vrai terminal, sinon rien de tout cela n'y
serait atteignable — le tube même qui fermait la TUI.
Assisted-by: Claude Opus 5
(cherry picked from commit 9e6c88622a74f7be408a8b1c523838eeadda1da3)
2026-08-15 03:41:56 -04:00
" Missing: " : {
" fr " : " Manquantes : " ,
" en " : " Missing: " ,
} ,
" No module file of that name in " : {
" fr " : " Aucun fichier de module de ce nom dans " ,
" en " : " No module file of that name in " ,
} ,
" the target no longer ships it, so there is nothing to " : {
" fr " : " la cible ne le livre plus, il n ' y a donc rien sur quoi " ,
" en " : " the target no longer ships it, so there is nothing to " ,
} ,
" fall back on. Read the copy before dropping it. " : {
" fr " : " retomber. Lire la copie avant de l ' abandonner. " ,
" en " : " fall back on. Read the copy before dropping it. " ,
} ,
" The copy is identical to the module file. " : {
" fr " : " La copie est identique au fichier du module. " ,
" en " : " The copy is identical to the module file. " ,
} ,
" line(s): that is what resetting gives up. " : {
" fr " : " ligne(s) : c ' est ce que la réinitialisation abandonne. " ,
" en " : " line(s): that is what resetting gives up. " ,
} ,
" What do you want to do with these customizations? " : {
" fr " : " Que voulez-vous faire de ces personnalisations ? " ,
" en " : " What do you want to do with these customizations? " ,
} ,
" Enter = nothing " : {
" fr " : " Entrée = rien " ,
" en " : " Enter = nothing " ,
} ,
" what the copy changed " : {
" fr " : " ce que la copie a changé " ,
" en " : " what the copy changed " ,
} ,
" reset them onto the module file " : {
" fr " : " les réinitialiser sur le fichier du module " ,
" en " : " reset them onto the module file " ,
} ,
" Full screen view unavailable. " : {
" fr " : " Vue plein écran indisponible. " ,
" en " : " Full screen view unavailable. " ,
} ,
" Saved before resetting " : {
" fr " : " Sauvegardé avant réinitialisation " ,
" en " : " Saved before resetting " ,
} ,
" Reset failed, nothing was changed. " : {
" fr " : " La réinitialisation a échoué, rien n ' a été modifié. " ,
" en " : " Reset failed, nothing was changed. " ,
} ,
" Reset done. " : {
" fr " : " Réinitialisation faite. " ,
" en " : " Reset done. " ,
} ,
" This copy uses variables that " : {
" fr " : " Cette copie utilise des variables que " ,
" en " : " This copy uses variables that " ,
} ,
" no longer defines " : {
" fr " : " ne définit plus " ,
" en " : " no longer defines " ,
} ,
" The copy was written against an older version and frozen " : {
" fr " : " La copie a été écrite contre une version antérieure et figée " ,
" en " : " The copy was written against an older version and frozen " ,
} ,
" there. The module has since renamed what it relies on. " : {
" fr " : " là. Le module a depuis renommé ce dont elle dépend. " ,
" en " : " there. The module has since renamed what it relies on. " ,
} ,
" Resetting restores " : {
" fr " : " La réinitialisation rend " ,
" en " : " Resetting restores " ,
} ,
" (nothing: the target no longer ships this file) " : {
" fr " : " (rien : la cible ne livre plus ce fichier) " ,
" en " : " (nothing: the target no longer ships this file) " ,
} ,
" Reset command copied. " : {
" fr " : " Commande de réinitialisation copiée. " ,
" en " : " Reset command copied. " ,
} ,
[FIX] migration: reset the stale SCSS after the bump, not before
The fix was offered while the checkout was still on the previous
version. Answering « a » ran reset_asset in an Odoo 12 shell, which has
no web_editor.assets: KeyError, nothing changed, and the migration went
on to break at the next bump — measured on a real run.
Predicting early is right; fixing early is not. The early call is now
--report-only, and a second call comes after the bump, on the upgraded
database, where the checkout can do it. The tool also refuses on its
own: it reads the checkout sources for reset_asset rather than trusting
a version number, and says when to come back.
--- FR ---
[FIX] migration : réinitialiser le SCSS périmé après le palier, pas avant
La correction était proposée alors que le checkout était encore sur la
version précédente. Répondre « a » lançait reset_asset dans un shell
Odoo 12, sans web_editor.assets : KeyError, rien de modifié, et la
migration continuait jusqu'à casser au palier suivant. Mesuré.
Prédire tôt est juste ; corriger tôt ne l'est pas. L'appel précoce est
désormais --report-only, et un second vient après le palier, sur la
base montée de version, là où le checkout sait le faire. L'outil refuse
aussi de lui-même : il cherche reset_asset dans les sources du checkout
plutôt que de se fier à un numéro, et dit quand revenir.
Assisted-by: Claude Opus 5
(cherry picked from commit 13d4d0a33c63efcbafb5fb29313a5377ba5f508e)
2026-08-15 16:34:26 -04:00
" Resetting needs Odoo 13.0 or later; this checkout is on " : {
" fr " : " La réinitialisation exige Odoo 13.0 ou plus ; ce checkout est sur " ,
" en " : " Resetting needs Odoo 13.0 or later; this checkout is on " ,
} ,
" The prediction stands, the fix does not: run it again once " : {
" fr " : " La prédiction tient, pas la correction : relancer une fois " ,
" en " : " The prediction stands, the fix does not: run it again once " ,
} ,
" the bump is done, on the upgraded database. " : {
" fr " : " le palier passé, sur la base montée de version. " ,
" en " : " the bump is done, on the upgraded database. " ,
} ,
" Applying it from here fails with " : {
" fr " : " L ' appliquer d ' ici échoue sur " ,
" en " : " Applying it from here fails with " ,
} ,
" and changes nothing. " : {
" fr " : " et ne change rien. " ,
" en " : " and changes nothing. " ,
} ,
2026-08-16 03:39:37 -04:00
" Reset one of them onto its module view " : {
" fr " : " Réinitialiser l ' une d ' elles sur sa vue module " ,
" en " : " Reset one of them onto its module view " ,
} ,
" Drifted COW copies " : {
" fr " : " Copies COW ayant dérivé " ,
" en " : " Drifted COW copies " ,
} ,
" Which one(s) to reset onto the module view? " : {
" fr " : " Laquelle ou lesquelles réinitialiser sur la vue module ? " ,
" en " : " Which one(s) to reset onto the module view? " ,
} ,
" numbers separated by commas, a = all, empty = " : {
" fr " : " numéros séparés par des virgules, a = toutes, vide = " ,
" en " : " numbers separated by commas, a = all, empty = " ,
} ,
" Kept. Nothing was reset. " : {
" fr " : " Conservées. Rien n ' a été réinitialisé. " ,
" en " : " Kept. Nothing was reset. " ,
} ,
" Unknown choice, nothing was reset. " : {
" fr " : " Choix inconnu, rien n ' a été réinitialisé. " ,
" en " : " Unknown choice, nothing was reset. " ,
} ,
[FIX] addons: stop calling a leftover report a failed command
« Command returned error code: 1 » followed a theme that was removed
correctly. 1 is this toolkit's « there are findings », but the report
was the uninstaller's last command, so its code became the script's;
and the COW check was still read through the capturing executor, which
announces any non-zero code as an error.
The leftovers can now be dealt with instead of only listed: keep by
default, or delete after their content is written to private/. Listing
fifteen attachments and stopping there meant composing an unlink() by
hand, mid-migration, from identifiers read off a screen.
--- FR ---
[FIX] addons : cesser d'appeler « erreur » un rapport de restes
« Command returned error code: 1 » suivait un thème correctement
retiré. 1 veut dire « il y a des constats » dans cet outillage, mais le
rapport était la dernière commande du désinstalleur, donc son code
devenait celui du script ; et la vérification COW passait encore par
l'exécuteur qui capture, lequel annonce tout code non nul comme une
erreur.
Les restes peuvent désormais être traités, pas seulement listés :
garder par défaut, ou effacer après écriture de leur contenu sous
private/. Lister quinze pièces jointes et s'arrêter là revenait à faire
composer un unlink() à la main, en pleine migration.
Assisted-by: Claude Opus 5
(cherry picked from commit 304ce32b2680e3d01e38c3224bb85c2798cd6f53)
2026-08-16 05:33:28 -04:00
" Delete these leftovers, or keep them? " : {
" fr " : " Effacer ces restes, ou les garder ? " ,
" en " : " Delete these leftovers, or keep them? " ,
} ,
" Enter = keep " : {
" fr " : " Entrée = garder " ,
" en " : " Enter = keep " ,
} ,
" delete, after saving them " : {
" fr " : " effacer, après les avoir sauvegardés " ,
" en " : " delete, after saving them " ,
} ,
" Kept. Nothing was deleted. " : {
" fr " : " Conservés. Rien n ' a été effacé. " ,
" en " : " Kept. Nothing was deleted. " ,
} ,
" Saved before deleting " : {
" fr " : " Sauvegardé avant effacement " ,
" en " : " Saved before deleting " ,
} ,
" Deletion failed, nothing was removed. " : {
" fr " : " L ' effacement a échoué, rien n ' a été retiré. " ,
" en " : " Deletion failed, nothing was removed. " ,
} ,
" attachment(s) deleted. " : {
" fr " : " pièce(s) jointe(s) effacée(s). " ,
" en " : " attachment(s) deleted. " ,
} ,
[ADD] migration: request every public URL before calling it done
A migration can load every module, log nothing, and still serve 500s on
pages nobody thought to open. Measured on the real one: 2 of 33 public
URLs failed — a blog post and /contactus — after a bump the log called
successful.
The list is the sitemap, what Odoo publishes for search engines. It
cannot be read from odoo-bin shell: enumerate_pages() asks for
http.root.get_db_router(request.db) and raises « object unbound »
without a real request. So the server is started on its own port,
asked, and always stopped — a forgotten one holds the port and fails
the next bump.
Offered before the Selenium prompt, default no: it boots a server and
can take minutes.
--- FR ---
[ADD] migration : interroger chaque URL publique avant de conclure
Une migration peut charger tous ses modules, ne rien écrire au journal,
et servir quand même des 500 sur des pages que personne n'ouvre.
Mesuré : 2 URL publiques sur 33 échouaient — un billet de blogue et
/contactus — après un palier que le journal disait réussi.
La liste est le sitemap, celle qu'Odoo publie pour les moteurs. Elle ne
se lit pas depuis odoo-bin shell : enumerate_pages() réclame
http.root.get_db_router(request.db) et lève « object unbound » sans
requête réelle. Le serveur est donc démarré sur son propre port,
interrogé, et toujours arrêté — un serveur oublié tient le port et fait
échouer le palier suivant.
Proposé avant l'invite Selenium, par défaut non : cela démarre un
serveur et peut durer.
Assisted-by: Claude Opus 5
(cherry picked from commit 277e85b3d06173beeb92be792467751b31ac0696)
2026-08-16 05:50:00 -04:00
" Request every public URL of this database now? " : {
" fr " : " Interroger maintenant toutes les URL publiques de cette base ? " ,
" en " : " Request every public URL of this database now? " ,
} ,
" The sitemap listed no URL: nothing was tested. " : {
" fr " : " Le sitemap n ' a listé aucune URL : rien n ' a été testé. " ,
" en " : " The sitemap listed no URL: nothing was tested. " ,
} ,
" public URL(s) answered without error. " : {
" fr " : " URL publique(s) ont répondu sans erreur. " ,
" en " : " public URL(s) answered without error. " ,
} ,
" public URL(s) failed " : {
" fr " : " URL publique(s) ont échoué " ,
" en " : " public URL(s) failed " ,
} ,
" no answer " : {
" fr " : " aucune réponse " ,
" en " : " no answer " ,
} ,
" A page listed for search engines that does not answer is " : {
" fr " : " Une page listée pour les moteurs de recherche qui ne répond pas est " ,
" en " : " A page listed for search engines that does not answer is " ,
} ,
" a page your visitors do not reach either. " : {
" fr " : " une page que vos visiteurs n ' atteignent pas non plus. " ,
" en " : " a page your visitors do not reach either. " ,
} ,
" The server never answered on " : {
" fr " : " Le serveur n ' a jamais répondu sur " ,
" en " : " The server never answered on " ,
} ,
" Could not read " : {
" fr " : " Impossible de lire " ,
" en " : " Could not read " ,
} ,
" Starting Odoo on " : {
" fr " : " Démarrage d ' Odoo sur " ,
" en " : " Starting Odoo on " ,
} ,
" port " : {
" fr " : " port " ,
" en " : " port " ,
} ,
[ADD] migration: name the views behind a failing URL, and offer the reset
The smoke test stopped at « these URLs answer 500 ». Turning that into a
fix meant reading an id out of a traceback and translating it to a key
by hand, mid-migration — the copy where a character goes missing.
It now reads the error context Odoo logs — [view_id: N, parent_id: M],
the one line it does not translate — resolves the parent to its key,
offers the reset numbered with « all », and re-requests the failing URLs
afterwards. Applying without re-asking would be calling it fixed
without having seen it answer.
Two defects found while measuring, both mine. ./run.sh is a bash
wrapper: terminate() killed it and left odoo-bin holding the port, six
orphans in six runs, each later run silently querying the first one's
server. And the log was read before stopping, so only the 24 startup
lines existed — hence « no view in cause » on pages that named one.
--- FR ---
[ADD] migration : nommer les vues derrière une URL en échec, et corriger
Le test s'arrêtait à « ces URL répondent 500 ». En faire un correctif
demandait de relever un id dans une trace et de le traduire en clé à la
main, en pleine migration — la recopie où un caractère se perd.
Il lit désormais le contexte qu'Odoo journalise — [view_id: N,
parent_id: M], la seule ligne qu'il ne traduit pas —, résout le parent
en clé, propose la réinitialisation numérotée avec « toutes », et
redemande ensuite les URL en échec. Appliquer sans redemander, ce
serait déclarer réparé sans l'avoir vu répondre.
Deux défauts trouvés en mesurant, tous deux à moi. ./run.sh est une
enveloppe bash : terminate() la tuait et laissait odoo-bin tenir le
port, six orphelins en six essais, chaque essai suivant interrogeant
sans le savoir le serveur du premier. Et le journal était lu avant
l'arrêt, donc seules les 24 lignes de démarrage existaient — d'où
« aucune vue en cause » sur des pages qui en nommaient une.
Assisted-by: Claude Opus 5
(cherry picked from commit 30fbcd1aee20c4028ea6b24bd04eb518f0587ffe)
2026-08-16 06:18:45 -04:00
" parent view(s) in cause " : {
" fr " : " vue(s) parente(s) en cause " ,
" en " : " parent view(s) in cause " ,
} ,
" Those parents are copies frozen on an older version; " : {
" fr " : " Ces parents sont des copies figées sur une version antérieure ; " ,
" en " : " Those parents are copies frozen on an older version; " ,
} ,
" resetting them onto the module view is the fix " : {
" fr " : " les réinitialiser sur la vue module est le correctif " ,
" en " : " resetting them onto the module view is the fix " ,
} ,
" No parent view named: nothing to offer. " : {
" fr " : " Aucune vue parente nommée : rien à proposer. " ,
" en " : " No parent view named: nothing to offer. " ,
} ,
" Copies to reset onto their module view " : {
" fr " : " Copies à réinitialiser sur leur vue module " ,
" en " : " Copies to reset onto their module view " ,
} ,
" Which one(s) to reset? " : {
" fr " : " Laquelle ou lesquelles réinitialiser ? " ,
" en " : " Which one(s) to reset? " ,
} ,
" Re-checked the " : {
" fr " : " Revérification des " ,
" en " : " Re-checked the " ,
} ,
" failing URL(s) after the reset " : {
" fr " : " URL en échec après la réinitialisation " ,
" en " : " failing URL(s) after the reset " ,
} ,
" Something already listens on port " : {
" fr " : " Quelque chose écoute déjà sur le port " ,
" en " : " Something already listens on port " ,
} ,
" it would be tested instead of this database. " : {
" fr " : " il serait testé à la place de cette base. " ,
" en " : " it would be tested instead of this database. " ,
} ,
[ADD] migration: Enter targets the highest supported Odoo version
Migrating means going all the way, and that choice had to be typed
every time. Enter did nothing visible: click.prompt without a default
re-asks on an empty line without printing anything, so a default added
without `default=""` would never have been reached and nothing would
have said so.
The highest is computed, not read off the end of the list: display
order is not a guarantee, and « 9.0 » sorts after « 18.0 » as a string.
The prompt announces it — a default nobody sees is a default nobody
uses. No choice at all means no default rather than a crash.
--- FR ---
[ADD] migration : Entrée vise la version Odoo la plus élevée
Migrer, c'est aller au bout, et ce choix devait être tapé chaque fois.
Entrée ne faisait rien de visible : click.prompt sans défaut redemande
sur une ligne vide sans rien afficher, si bien qu'un défaut ajouté sans
`default=""` n'aurait jamais été atteint, sans que rien ne le signale.
La plus haute est calculée, pas lue au bout de la liste : l'ordre
d'affichage n'est pas une garantie, et « 9.0 » se trie après « 18.0 »
en chaînes. L'invite l'annonce — un défaut qu'on ne montre pas est un
défaut que personne n'utilise. Aucun choix possible ne donne aucun
défaut plutôt qu'une erreur.
Assisted-by: Claude Opus 5
(cherry picked from commit f0861363f496cdc3c0bb02216d30dd0aa8d3697b)
2026-08-16 07:11:43 -04:00
" Enter = " : {
" fr " : " Entrée = " ,
" en " : " Enter = " ,
} ,
[FIX] migration: reset the copy that is actually stale, and say when a key misses
Answering « all » reset one copy of two and reported success. Two
defects behind it, both mine.
A requested key matching no finding did nothing, silently: the tool
returned early on « nothing drifted » and never looked. Detection is
differential — it only sees a copy whose CHILD breaks — so a stale copy
without children escapes it. A key can now be reset outside detection,
an unknown one is named, and the exit code is 2.
And the culprit is not always the parent. On /contactus the parent was
identical to its module view; the CHILD held the stale arch. Both are
proposed now, parent first — it is the more common case.
Measured on the migration: 33 of 33 public URLs answer.
--- FR ---
[FIX] migration : réinitialiser la copie vraiment périmée, et signaler une clé sans objet
Répondre « toutes » réinitialisait une copie sur deux et annonçait un
succès. Deux défauts derrière, tous deux à moi.
Une clé demandée ne correspondant à aucun constat ne faisait rien, en
silence : l'outil sortait sur « rien n'a dérivé » sans jamais chercher.
La détection est différentielle — elle ne voit qu'une copie dont un
ENFANT casse — donc une copie périmée sans enfant lui échappe. Une clé
peut désormais être réinitialisée hors détection, une clé inconnue est
nommée, et le code de sortie vaut 2.
Et le coupable n'est pas toujours le parent. Sur /contactus, le parent
était identique à sa vue module ; c'est l'ENFANT qui portait l'arch
périmée. Les deux sont proposés, le parent d'abord — le cas le plus
fréquent.
Mesuré sur la migration : 33 URL publiques sur 33 répondent.
Assisted-by: Claude Opus 5
2026-08-17 04:49:32 -04:00
" No COW copy carries this key " : {
" fr " : " Aucune copie COW ne porte cette clé " ,
" en " : " No COW copy carries this key " ,
} ,
" already identical to the module view. " : {
" fr " : " déjà identique à la vue module. " ,
" en " : " already identical to the module view. " ,
} ,
" Odoo surfaces this when the module view is rewritten " : {
" fr " : " Odoo le fait apparaître quand la vue module est réécrite " ,
" en " : " Odoo surfaces this when the module view is rewritten " ,
} ,
" (a version bump) or when the page is rendered. " : {
" fr " : " (un palier de version) ou quand la page est rendue. " ,
" en " : " (a version bump) or when the page is rendered. " ,
} ,
2026-08-17 05:13:13 -04:00
" Before starting: what already answers, and what " : {
" fr " : " Avant de commencer : ce qui répond déjà, et ce qui " ,
" en " : " Before starting: what already answers, and what " ,
} ,
" does not. A page broken now will still be broken " : {
" fr " : " ne répond pas. Une page cassée maintenant le sera encore " ,
" en " : " does not. A page broken now will still be broken " ,
} ,
" after — and that is not the migration. " : {
" fr " : " après — et ce ne sera pas la migration. " ,
" en " : " after — and that is not the migration. " ,
} ,
[ADD] migration: browse the drifted COW copies full screen
The text report puts a thousand lines between the two things that decide:
what the copy holds that the module view does not — all a reset gives up
— and which child no longer finds its anchor, which is why anything
breaks. Space switches between them, « c » copies the reset command.
Offered as [4] on the error prompt, next to check and reset, and run on
the real terminal: a full screen started through the capturing executor
falls back to the text report with nothing to tell the two apart.
The diff is rendered in one place, shared with the command line. Two
renderings would drift without anything saying so.
--- FR ---
[ADD] migration : parcourir les copies COW dérivées en plein écran
Le rapport texte met mille lignes entre les deux choses qui décident :
ce que la copie porte et que la vue module n'a pas — tout ce qu'une
réinitialisation abandonne — et quel enfant ne trouve plus son ancrage,
la raison pour laquelle quoi que ce soit casse. L'espace bascule, « c »
copie la commande de réparation.
Proposé en [4] à l'invite d'erreur, à côté de vérifier et réinitialiser,
et lancé sur le vrai terminal : un plein écran passé par l'exécuteur qui
capture retombe sur le rapport texte, sans rien qui les distingue.
Le diff est rendu à un seul endroit, partagé avec la ligne de commande.
Deux rendus dériveraient sans que rien ne le signale.
Assisted-by: Claude Opus 5
2026-08-17 06:29:25 -04:00
" No child fails on this copy: it drifted without " : {
" fr " : " Aucun enfant n ' échoue sur cette copie : elle a dérivé sans " ,
" en " : " No child fails on this copy: it drifted without " ,
} ,
" breaking anything yet. " : {
" fr " : " encore rien casser. " ,
" en " : " breaking anything yet. " ,
} ,
" These children no longer find their anchor in the copy " : {
" fr " : " Ces enfants ne trouvent plus leur point d ' ancrage dans la copie " ,
" en " : " These children no longer find their anchor in the copy " ,
} ,
" The anchor exists in the module view " : {
" fr " : " L ' ancrage existe dans la vue module " ,
" en " : " The anchor exists in the module view " ,
} ,
" but not in this copy, frozen on an older version. " : {
" fr " : " mais pas dans cette copie, figée sur une version antérieure. " ,
" en " : " but not in this copy, frozen on an older version. " ,
} ,
" Resetting restores the module arch; the customization the " : {
" fr " : " Réinitialiser rend l ' arch du module ; la personnalisation que " ,
" en " : " Resetting restores the module arch; the customization the " ,
} ,
" copy carried is saved first, and is yours to re-apply as an " : {
" fr " : " la copie portait est sauvegardée d ' abord, à vous de la réappliquer en " ,
" en " : " copy carried is saved first, and is yours to re-apply as an " ,
} ,
" INHERITING view with its own key. " : {
" fr " : " vue HÉRITANTE avec sa propre clé. " ,
" en " : " INHERITING view with its own key. " ,
} ,
" No module view carries this key: nothing to reset " : {
" fr " : " Aucune vue module ne porte cette clé : rien sur quoi " ,
" en " : " No module view carries this key: nothing to reset " ,
} ,
" onto. " : {
" fr " : " réinitialiser. " ,
" en " : " onto. " ,
} ,
" Browse the differences full screen " : {
" fr " : " Parcourir les différences en plein écran " ,
" en " : " Browse the differences full screen " ,
} ,
[ADD] migration: run the OCA database cleanup before testing the pages
The eight purges are not independent: purging a model frees the columns
that referenced it, purging a table frees the data pointing at it. One
pass is never enough, so the requested order runs again until a full
pass repairs nothing new.
Refusals are expected — a foreign key still holds, a module says no.
Purging a list at once loses everything to the first one, so each entry
purges inside its own savepoint: a refusal rolls back that entry alone.
What one pass could not take, the next may, once its neighbours are
gone. Leftovers are reported as a warning: a database can carry some
that nothing removes, and stopping there would help no one.
It refuses a version mismatch. Measured while building it: a shell on
Odoo 14 opened against a 17.0 database went rewriting ir_model before
dying on a jsonb it did not know. An older Odoo does not merely fail on
a newer database — it writes on the way.
--- FR ---
[ADD] migration : lancer le nettoyage OCA avant de tester les pages
Les huit purges ne sont pas indépendantes : purger un modèle libère les
colonnes qui le référençaient, purger une table libère les données qui
la visaient. Une passe ne suffit jamais, l'ordre demandé est donc rejoué
jusqu'à ce qu'une passe entière ne répare plus rien.
Les refus sont attendus — une clé étrangère tient, un module dit non.
Purger d'un bloc perdrait tout au premier : chaque entrée passe dans son
propre point de reprise, un refus n'emporte que la sienne. Ce qu'une
passe n'a pu prendre, la suivante le peut, une fois les voisines
parties. Les restes sont un avertissement : une base peut en porter que
rien ne retire, et s'arrêter là n'aiderait personne.
Il refuse une version qui ne correspond pas. Mesuré en le construisant :
un shell en Odoo 14 ouvert sur une base 17.0 est parti réécrire ir_model
avant de mourir sur un jsonb inconnu de lui. Un Odoo plus ancien
n'échoue pas simplement sur une base plus récente — il écrit en chemin.
Assisted-by: Claude Opus 5
2026-08-17 09:05:03 -04:00
" The checkout is on Odoo " : {
" fr " : " Le checkout est sur Odoo " ,
" en " : " The checkout is on Odoo " ,
} ,
" and the database on " : {
" fr " : " et la base sur " ,
" en " : " and the database on " ,
} ,
" Opening it with the wrong version writes to it before " : {
" fr " : " L ' ouvrir avec la mauvaise version y écrit avant " ,
" en " : " Opening it with the wrong version writes to it before " ,
} ,
" failing. Switch the checkout first. " : {
" fr " : " d ' échouer. Basculer le checkout d ' abord. " ,
" en " : " failing. Switch the checkout first. " ,
} ,
" Database cleanup on " : {
" fr " : " Nettoyage de la base " ,
" en " : " Database cleanup on " ,
} ,
" Cleaning " : {
" fr " : " Nettoyage de " ,
" en " : " Cleaning " ,
} ,
" pass " : {
" fr " : " passe " ,
" en " : " pass " ,
} ,
" purged " : {
" fr " : " purgés " ,
" en " : " purged " ,
} ,
" purged; " : {
" fr " : " purgés ; " ,
" en " : " purged; " ,
} ,
" could not be, and are left as they are " : {
" fr " : " n ' ont pas pu l ' être, et restent en l ' état " ,
" en " : " could not be, and are left as they are " ,
} ,
" entries purged, nothing left. " : {
" fr " : " entrées purgées, rien ne reste. " ,
" en " : " entries purged, nothing left. " ,
} ,
" no such wizard in this version, skipped. " : {
" fr " : " pas d ' assistant pour cela dans cette version, ignoré. " ,
" en " : " no such wizard in this version, skipped. " ,
} ,
" A database can carry leftovers nothing can remove. " : {
" fr " : " Une base peut porter des restes que rien ne peut retirer. " ,
" en " : " A database can carry leftovers nothing can remove. " ,
} ,
" This is a warning, not a failure. " : {
" fr " : " C ' est un avertissement, pas un échec. " ,
" en " : " This is a warning, not a failure. " ,
} ,
" The cleanup produced no report. " : {
" fr " : " Le nettoyage n ' a produit aucun rapport. " ,
" en " : " The cleanup produced no report. " ,
} ,
" Unreadable report " : {
" fr " : " Rapport illisible " ,
" en " : " Unreadable report " ,
} ,
" obsolete models " : {
" fr " : " modèles obsolètes " ,
" en " : " obsolete models " ,
} ,
" obsolete modules " : {
" fr " : " modules obsolètes " ,
" en " : " obsolete modules " ,
} ,
" obsolete columns " : {
" fr " : " colonnes obsolètes " ,
" en " : " obsolete columns " ,
} ,
" obsolete tables " : {
" fr " : " tables obsolètes " ,
" en " : " obsolete tables " ,
} ,
" obsolete data entries " : {
" fr " : " entrées de données obsolètes " ,
" en " : " obsolete data entries " ,
} ,
" obsolete menu entries " : {
" fr " : " entrées de menu obsolètes " ,
" en " : " obsolete menu entries " ,
} ,
" missing indexes " : {
" fr " : " index manquants " ,
" en " : " missing indexes " ,
} ,
" obsolete properties " : {
" fr " : " propriétés obsolètes " ,
" en " : " obsolete properties " ,
} ,
" entries would be purged " : {
" fr " : " entrées seraient purgées " ,
" en " : " entries would be purged " ,
} ,
" nothing was changed " : {
" fr " : " rien n ' a été modifié " ,
" en " : " nothing was changed " ,
} ,
[ADD] migration: make Enter mean the answer you always give
Auto-run promised to "take the default after five seconds", but every
default was EMPTY: it took nothing. Worse, half the prompts of a
migration are asked by separate processes — the theme uninstaller, the
stale-SCSS detector, the smoke test. They knew nothing of auto-run and
waited forever for a keystroke that never came.
The countdown now lives in one file and travels through the ENVIRONMENT,
the only channel a fork shares. Enter takes the default everywhere, not
only under auto-run: a prompt that prints (Y/n) and does otherwise is
worse than no prompt. Every text was rewritten to say what Enter does,
and every default kept an explicit way out.
Two defaults now write. Both are tenable only because the backup runs
first, and a test locks that ORDER rather than trusting a promise.
--- FR ---
[ADD] migration : faire d'Entrée la réponse qu'on donne toujours
L'auto-exécution promettait « le défaut après cinq secondes », mais tous
les défauts étaient VIDES : elle ne prenait rien. Pire, la moitié des
invites d'une migration sont posées par d'autres processus — le
désinstalleur de thème, le détecteur de SCSS figé, le test de fumée. Ils
ignoraient l'auto-exécution et attendaient sans fin une frappe.
Le compte à rebours tient désormais dans un seul fichier et voyage par
l'ENVIRONNEMENT, le seul canal qu'un fork partage. Entrée vaut le défaut
partout, pas seulement en auto : une invite qui affiche (Y/n) et fait
l'inverse est pire que pas d'invite. Chaque texte dit ce que fait Entrée,
et chaque défaut garde une issue explicite.
Deux défauts écrivent. Ils ne tiennent que parce que la sauvegarde passe
d'abord, et un test verrouille cet ORDRE plutôt qu'une promesse.
Assisted-by: Claude Opus 5
2026-08-17 20:47:17 -04:00
" Enter = 1 " : {
" fr " : " Entrée = 1 " ,
" en " : " Enter = 1 " ,
} ,
" Enter = all, n = none " : {
" fr " : " Entrée = tous, n = aucun " ,
" en " : " Enter = all, n = none " ,
} ,
" Enter = delete, after saving them " : {
" fr " : " Entrée = effacer, après les avoir sauvegardés " ,
" en " : " Enter = delete, after saving them " ,
} ,
" Enter = neutralize now, reversible " : {
" fr " : " Entrée = neutraliser maintenant, réversible " ,
" en " : " Enter = neutralize now, reversible " ,
} ,
" Enter = reset them onto the module file " : {
" fr " : " Entrée = les réinitialiser sur le fichier du module " ,
" en " : " Enter = reset them onto the module file " ,
} ,
" Enter upgrades all addons of the " : {
" fr " : " Entrée met à jour tous les modules de la " ,
" en " : " Enter upgrades all addons of the " ,
} ,
" Open it at EVERY version bump, stop asking " : {
" fr " : " L ' ouvrir à CHAQUE palier de version, ne plus demander " ,
" en " : " Open it at EVERY version bump, stop asking " ,
} ,
" Testing every bump, as chosen earlier. " : {
" fr " : " Test à chaque palier, comme choisi plus tôt. " ,
" en " : " Testing every bump, as chosen earlier. " ,
} ,
" numbers separated by commas, Enter = all, n = " : {
" fr " : " numéros séparés par des virgules, Entrée = toutes, n = " ,
" en " : " numbers separated by commas, Enter = all, n = " ,
} ,
" decide at the version bump " : {
" fr " : " décider au palier de version " ,
" en " : " decide at the version bump " ,
} ,
" keep " : {
" fr " : " garder " ,
" en " : " keep " ,
} ,
[ADD] migration: open every app as the neutralization test user
The public smoke test opens what a visitor reaches. It says nothing about
the back office, which is where a migration does most of its damage: a
field dropped from a model but still named in a form, a module installed
in the database whose code no longer ships with the target version. None
of it stops the module loading — it stops the day someone opens the app.
Neutralizing installs a `test` / `test` login carrying the SYSTEM user's
groups, and it survives the module's uninstall. So the tool checks for
that user rather than trusting a flag, and it rides the server the public
pass already started: booting Odoo is what costs minutes, not requests.
Measured on a real 18.0 database of 25 apps, which found four defects of
mine: web_search_read changed signature in 17, a bare 404 hides an
unregistered model, embedded sub-views are not the parent's fields, and
an empty psql result meant two different things.
--- FR ---
[ADD] migration : ouvrir chaque application avec l'utilisateur test
Le test de fumée public ouvre ce qu'un visiteur atteint. Il ne dit rien du
back-office, où une migration fait pourtant l'essentiel de ses dégâts : un
champ retiré du modèle mais toujours nommé dans un formulaire, un module
installé en base dont le code n'accompagne plus la version cible. Rien de
cela n'arrête le chargement ; cela arrête le jour où l'on ouvre l'appli.
La neutralisation pose un compte `test` / `test` portant les groupes du
superutilisateur, et il survit à la désinstallation du module. L'outil
vérifie donc cet utilisateur plutôt qu'un drapeau, et réutilise le serveur
déjà démarré : c'est le démarrage qui coûte, pas les requêtes.
Mesuré sur une vraie base 18.0 de 25 applications, ce qui a révélé quatre
défauts de mon fait.
Assisted-by: Claude Opus 5
2026-08-17 22:16:40 -04:00
" The server did not serve the login page. " : {
" fr " : " Le serveur n ' a pas servi la page de connexion. " ,
" en " : " The server did not serve the login page. " ,
} ,
" Wrong login or password for user " : {
" fr " : " Identifiant ou mot de passe refusé pour l ' utilisateur " ,
" en " : " Wrong login or password for user " ,
} ,
" Could not read the menus " : {
" fr " : " Impossible de lire les menus " ,
" en " : " Could not read the menus " ,
} ,
" Apps opened as the test user " : {
" fr " : " Applications ouvertes avec l ' utilisateur test " ,
" en " : " Apps opened as the test user " ,
} ,
" not openable this way (client or server " : {
" fr " : " non ouvrables ainsi (action client ou " ,
" en " : " not openable this way (client or server " ,
} ,
" action): nothing to render. " : {
" fr " : " serveur) : rien à rendre. " ,
" en " : " action): nothing to render. " ,
} ,
" opened with an empty domain: theirs " : {
" fr " : " ouvertes avec un domaine vide : le leur " ,
" en " : " opened with an empty domain: theirs " ,
} ,
" needs the browser to evaluate it. " : {
" fr " : " demande le navigateur pour être évalué. " ,
" en " : " needs the browser to evaluate it. " ,
} ,
" Every app opened its first page. " : {
" fr " : " Chaque application a ouvert sa première page. " ,
" en " : " Every app opened its first page. " ,
} ,
" Apps that failed to open " : {
" fr " : " Applications qui n ' ont pas pu s ' ouvrir " ,
" en " : " Apps that failed to open " ,
} ,
" reading the action " : {
" fr " : " lecture de l ' action " ,
" en " : " reading the action " ,
} ,
" rendering the views " : {
" fr " : " rendu des vues " ,
" en " : " rendering the views " ,
} ,
" loading the first records " : {
" fr " : " chargement des premiers enregistrements " ,
" en " : " loading the first records " ,
} ,
" These are back-office failures: the public smoke test " : {
" fr " : " Ce sont des échecs du back-office : le test de fumée public " ,
" en " : " These are back-office failures: the public smoke test " ,
} ,
" cannot see them. " : {
" fr " : " ne peut pas les voir. " ,
" en " : " cannot see them. " ,
} ,
" Could not log in as " : {
" fr " : " Connexion impossible en tant que " ,
" en " : " Could not log in as " ,
} ,
" No " : {
" fr " : " Aucun " ,
" en " : " No " ,
} ,
" user in " : {
" fr " : " utilisateur dans " ,
" en " : " user in " ,
} ,
" the database was not neutralized, nothing to browse. " : {
" fr " : " la base n ' a pas été neutralisée, rien à parcourir. " ,
" en " : " the database was not neutralized, nothing to browse. " ,
} ,
" Back office not browsed " : {
" fr " : " Back-office non parcouru " ,
" en " : " Back office not browsed " ,
} ,
" no test user: the database was not neutralized " : {
" fr " : " pas d ' utilisateur test : la base n ' a pas été neutralisée " ,
" en " : " no test user: the database was not neutralized " ,
} ,
" Could not tell whether the " : {
" fr " : " Impossible de savoir si l ' utilisateur " ,
" en " : " Could not tell whether the " ,
} ,
" user exists in " : {
" fr " : " existe dans " ,
" en " : " user exists in " ,
} ,
" could not tell whether the test user exists " : {
" fr " : " impossible de savoir si l ' utilisateur test existe " ,
" en " : " could not tell whether the test user exists " ,
} ,
" listing the model fields " : {
" fr " : " inventaire des champs du modèle " ,
" en " : " listing the model fields " ,
} ,
" model absent from the running registry " : {
" fr " : " modèle absent du registre en cours " ,
" en " : " model absent from the running registry " ,
} ,
" installed in the database, but its module code " : {
" fr " : " installé dans la base, mais le code de son module " ,
" en " : " installed in the database, but its module code " ,
} ,
" is not in this addons path. " : {
" fr " : " n ' est pas dans cet addons path. " ,
" en " : " is not in this addons path. " ,
} ,
" Views naming a field the model no longer has " : {
" fr " : " Vues nommant un champ que le modèle n ' a plus " ,
" en " : " Views naming a field the model no longer has " ,
} ,
" Public pages, then the back office as the test user " : {
" fr " : " Les pages publiques, puis le back-office avec l ' utilisateur test " ,
" en " : " Public pages, then the back office as the test user " ,
} ,
" if the database was neutralized. " : {
" fr " : " si la base a été neutralisée. " ,
" en " : " if the database was neutralized. " ,
} ,
[ADD] migration: open /my as the test user, and blame the right request
The portal is a third rendering, neither the public site nor the back
office: QWeb frontend with counters that each query their own model. The
sitemap does not list /my — the page needs a session — and no RPC call
goes through it. A migration can break it with nothing else noticing.
Measured on a real database: /my answered 500 on a user field a module no
longer defines, while every public page and sixteen apps were fine.
Two guards the run proved necessary. Requested without a session, /my
redirects to the login form and answers 200: counting that as a success
would announce a healthy portal never seen. And the production error page
shows no traceback, so the reason comes from the server log — attributed
by PATH, because the portal is checked first and taking the last trace
blamed it for an app's failure. A wrong cause costs more than none.
--- FR ---
[ADD] migration : ouvrir /my avec l'utilisateur test, et accuser la bonne requête
Le portail est un troisième rendu, ni le site public ni le back-office :
du QWeb frontend, avec ses compteurs qui interrogent chacun leur modèle.
Le sitemap ne liste pas /my — la page demande une session — et aucun appel
RPC n'y passe. Une migration peut le casser sans que rien ne le dise.
Mesuré sur une vraie base : /my rendait 500 sur un champ utilisateur qu'un
module ne définit plus, alors que les pages publiques et seize
applications allaient bien.
Deux garde-fous que l'essai a rendus nécessaires. Sans session, /my
redirige vers le formulaire de connexion et rend 200 : le compter pour une
réussite annoncerait un portail jamais vu. Et la page d'erreur de
production ne montre aucune trace : la raison vient du journal, attribuée
par CHEMIN — le portail est interrogé en premier, et prendre la dernière
trace l'accusait de la panne d'une application.
Assisted-by: Claude Opus 5
2026-08-17 22:48:40 -04:00
" Portal " : {
" fr " : " Portail " ,
" en " : " Portal " ,
} ,
" Portal pages opened as the test user " : {
" fr " : " Pages du portail ouvertes avec l ' utilisateur test " ,
" en " : " Portal pages opened as the test user " ,
} ,
" not signed in " : {
" fr " : " pas connecté " ,
" en " : " not signed in " ,
} ,
" the page served was the login form " : {
" fr " : " la page servie était le formulaire de connexion " ,
" en " : " the page served was the login form " ,
} ,
" server log " : {
" fr " : " journal du serveur " ,
" en " : " server log " ,
} ,
[FIX] database_cleanup: purge the batch, and say what you are doing
Seventeen minutes with no output looks exactly like an infinite loop. It
was not one: measured on the running process, zero lock waits and queries
changing every sample. It was working, silently, one module every thirty
seconds.
Both halves were mine. Purging line by line — added so one refusal could
not sink a category — calls button_immediate_uninstall() per line, and
that reloads the WHOLE registry: 5984 modules re-read each time. The
batch is now purged in one call, exactly as the OCA wizard intends, and
the per-line isolation only costs when a refusal actually happens.
And the tool captured its child's output, so nothing showed before the
end. It now relays each step as it arrives, with elapsed seconds, and a
timer kills the process group when the deadline passes.
--- FR ---
[FIX] database_cleanup : purger le lot, et dire ce qu'on fait
Dix-sept minutes sans une ligne ressemblent trait pour trait à une boucle
infinie. Ce n'en était pas une : mesuré sur le processus vivant, zéro
verrou en attente et des requêtes qui changeaient à chaque instantané. Il
travaillait, en silence, à raison d'un module toutes les trente secondes.
Les deux moitiés étaient de mon fait. Purger ligne par ligne — ajouté
pour qu'un refus n'emporte pas la catégorie — appelle
button_immediate_uninstall() par ligne, ce qui recharge TOUT le registre :
5984 modules relus à chaque fois. Le lot est désormais purgé en un seul
appel, comme le module OCA le prévoit, et l'isolement ne coûte que
lorsqu'un refus survient vraiment.
Et l'outil capturait la sortie de son enfant : rien avant la fin. Il
relaie maintenant chaque étape, temps écoulé compris.
Assisted-by: Claude Opus 5
2026-08-17 23:19:04 -04:00
" Purging modules reloads the whole registry: on a big " : {
" fr " : " Purger les modules recharge tout le registre : sur un grand " ,
" en " : " Purging modules reloads the whole registry: on a big " ,
} ,
" addons path this takes minutes, not seconds. " : {
" fr " : " addons path cela prend des minutes, pas des secondes. " ,
" en " : " addons path this takes minutes, not seconds. " ,
} ,
" Took " : {
" fr " : " Durée : " ,
" en " : " Took " ,
} ,
" seconds. " : {
" fr " : " secondes. " ,
" en " : " seconds. " ,
} ,
" pass " : {
" fr " : " passe " ,
" en " : " pass " ,
} ,
" The cleanup was still running after " : {
" fr " : " Le nettoyage tournait encore après " ,
" en " : " The cleanup was still running after " ,
} ,
" seconds and was stopped. " : {
" fr " : " secondes et a été arrêté. " ,
" en " : " seconds and was stopped. " ,
} ,
" the whole batch " : {
" fr " : " le lot entier " ,
" en " : " the whole batch " ,
} ,
[FIX] migration: repair, replay, and know when to stop
The failure that stops an upgrade is almost always the same one — a COW
copy left behind on the previous version — and the repair is known. So
Enter now repairs, and a repair that actually changed something replays
the command by itself.
A default that acts must know when to stop, and here it must twice over.
Repairing when there is nothing left to repair, then offering it again,
loops without end: measured, « no COW copy has drifted » over and over.
And a repair that does not help would replay the command forever. Three
attempts, then the prompt says plainly that this one needs a developer.
The turn counter is deliberately redundant with that logic: both guard
the same failure, but the counter holds even if the logic is broken one
day by accident. An endless loop in an unattended migration costs a
night.
Also: the smoke tool forced `ask=input` on its own prompt, which
short-circuited auto-run — the question waited for a keystroke nobody
was there to give.
--- FR ---
[FIX] migration : réparer, rejouer, et savoir s'arrêter
L'échec qui arrête une migration est presque toujours le même — une copie
COW restée sur la version d'avant — et la réparation est connue. Entrée
répare donc, et une réparation qui a vraiment changé quelque chose rejoue
la commande d'elle-même.
Un défaut qui agit doit savoir s'arrêter, et ici deux fois plutôt qu'une.
Réparer quand il n'y a plus rien à réparer, puis le reproposer, boucle
sans fin : mesuré, « Aucune copie COW n'a dérivé », encore et encore. Et
une réparation qui ne suffit pas rejouerait indéfiniment. Trois
tentatives, puis l'invite dit qu'il faut un développeur.
Le compteur de tours double volontairement cette logique : il tient même
si elle est cassée un jour par mégarde.
Assisted-by: Claude Opus 5
2026-08-18 00:08:31 -04:00
" Still failing after " : {
" fr " : " Échoue toujours après " ,
" en " : " Still failing after " ,
} ,
" attempts: this one needs a developer. " : {
" fr " : " tentatives : celle-ci demande un développeur. " ,
" en " : " attempts: this one needs a developer. " ,
} ,
" Error detected. Choose, or ctrl+c to " : {
" fr " : " Erreur détectée. Choisissez, ou ctrl+c pour " ,
" en " : " Error detected. Choose, or ctrl+c to " ,
} ,
" stop " : {
" fr " : " arrêter " ,
" en " : " stop " ,
} ,
" Too many turns on this prompt: moving on. " : {
" fr " : " Trop de tours sur cette invite : on passe. " ,
" en " : " Too many turns on this prompt: moving on. " ,
} ,
[FIX] migration: the module-upgrade step no longer waits forever
Six « press to continue » prompts on the module-code migration were bare
input() calls. Auto-run never reached them, so it stopped there and said
nothing — the question had been asked, after all. They now go through the
timed reader, where Enter and the countdown mean the same thing.
They were English too, on a path the rest of which speaks the system
language.
The guard that forbids a bare input() only looked at a handful of
methods, and internal_module_upgrade was not among them — which is
exactly how these six survived the first sweep. It is now covered, and
the module menu's own prompts are documented as deliberately blocking:
they ask for a module name, a path, a version, and « » after five
seconds would be a wrong answer, not a convenience.
--- FR ---
[FIX] migration : l'étape de migration de code n'attend plus indéfiniment
Six invites « appuyez pour continuer » de la migration de code étaient des
input() nus. L'auto-exécution ne les atteignait pas : elle s'arrêtait là
sans rien dire — la question avait bien été posée. Elles passent
désormais par le lecteur temporisé, où Entrée et le compte à rebours font
la même chose.
Elles étaient aussi en anglais, sur un chemin dont tout le reste parle la
langue du système.
Le garde-fou qui interdit les input() nus ne regardait qu'une poignée de
méthodes, et internal_module_upgrade n'en faisait pas partie — c'est
ainsi que ces six-là avaient survécu. Il la couvre maintenant, et les
invites du menu de modules sont documentées comme bloquantes à dessein :
elles demandent un nom, un chemin, une version.
Assisted-by: Claude Opus 5
2026-08-18 02:36:44 -04:00
" Please validate git commit on repos, press to " : {
" fr " : " Veuillez valider les commits sur les dépôts, appuyez pour " ,
" en " : " Please validate git commit on repos, press to " ,
} ,
" Check the migrated code, press to continue " : {
" fr " : " Vérifiez le code migré, appuyez pour continuer " ,
" en " : " Check the migrated code, press to continue " ,
} ,
" Please validate the repo is ready to run " : {
" fr " : " Veuillez valider que le dépôt est prêt pour " ,
" en " : " Please validate the repo is ready to run " ,
} ,
" press to continue " : {
" fr " : " appuyez pour continuer " ,
" en " : " press to continue " ,
} ,
[FIX] migration: a skipped back office must not read as a healthy one
The back-office pass was already there, and it works. What did not work
was the way it declined: one discreet line at the end of a long report,
saying « the database was not neutralized ». On six databases of a real
migration the test user is present up to the 15 bump and GONE at 17 and
18 — so the pass stopped silently exactly where a migration does the most
damage, and said something that was not even true.
The migration knows what it neutralized, so it now asks for the back
office by name. A missing test user on a database it neutralized is a
finding, printed loudly and counted as a failure. A missing tool is too:
returning None made the whole pass vanish without a word.
And it says UP FRONT which passes will run, on that database, by name.
--- FR ---
[FIX] migration : un back-office sauté ne doit pas se lire comme un sain
La passe back-office était déjà là et elle fonctionne. Ce qui ne
fonctionnait pas, c'est sa façon de renoncer : une ligne discrète en fin
d'un long rapport, disant « la base n'a pas été neutralisée ». Sur les six
bases d'une vraie migration, l'utilisateur test est présent jusqu'au
palier 15 et ABSENT en 17 et 18 — la passe s'arrêtait donc sans bruit là
où une migration fait le plus de dégâts, en disant quelque chose de faux.
La migration sait ce qu'elle a neutralisé : elle réclame désormais le
back-office. Un utilisateur test manquant sur une base qu'elle a
neutralisée est une trouvaille, affichée fort et comptée comme un échec.
Un outil absent aussi : rendre None faisait disparaître la passe entière.
Et elle annonce AVANT de lancer ce qui sera parcouru.
Assisted-by: Claude Opus 5
2026-08-19 03:21:25 -04:00
" smoke_internal_ui.py is missing " : {
" fr " : " smoke_internal_ui.py est absent " ,
" en " : " smoke_internal_ui.py is missing " ,
} ,
" the test user is gone from a neutralized database: the back office was NOT checked " : {
" fr " : " l ' utilisateur test a disparu d ' une base neutralisée : le back-office n ' a PAS été vérifié " ,
" en " : " the test user is gone from a neutralized database: the back office was NOT checked " ,
} ,
" Back office NOT browsed " : {
" fr " : " Back-office NON parcouru " ,
" en " : " Back office NOT browsed " ,
} ,
" Public pages, then the back office and /my as the " : {
" fr " : " Les pages publiques, puis le back-office et /my avec " ,
" en " : " Public pages, then the back office and /my as the " ,
} ,
" test user (neutralized database). " : {
" fr " : " l ' utilisateur test (base neutralisée). " ,
" en " : " test user (neutralized database). " ,
} ,
" Public pages only: " : {
" fr " : " Pages publiques seulement : " ,
" en " : " Public pages only: " ,
} ,
" was not neutralized, so there is no test user to " : {
" fr " : " n ' a pas été neutralisée, il n ' y a donc pas d ' utilisateur test pour " ,
" en " : " was not neutralized, so there is no test user to " ,
} ,
" sign in with. " : {
" fr " : " s ' y connecter. " ,
" en " : " sign in with. " ,
} ,
[ADD] migration: « t » shows where the migration stands
A migration crosses six bumps, runs hundreds of commands and lasts hours.
The journal said what had been LAUNCHED; it never said what came of it.
Three hours in, one reads two hundred command lines without knowing which
one failed, nor what the smoke test concluded.
« v » could not carry this: it already means « view the differences » in
three prompts, and a letter meaning two things is worse than a letter
meaning nothing. « t » was free, and it is the same everywhere — one
shared string carries both shortcuts, so no prompt can drift.
Looking is not answering: the same question comes back afterwards.
What was missing was the data. Failures and tool verdicts are now
recorded with the step they happened in, bounded so a progression file
cannot grow without end. A tool rerun after a repair keeps its LAST
verdict and the count of its runs — showing both without distinction
would read a repair as a lasting failure.
--- FR ---
[ADD] migration : « t » montre où en est la migration
Une migration traverse six paliers, lance des centaines de commandes et
dure des heures. Le journal disait ce qui avait été LANCÉ, jamais ce que
cela avait donné. Trois heures plus tard on relit deux cents lignes sans
savoir laquelle a échoué, ni ce que le test de fumée a conclu.
« v » ne pouvait pas porter cela : il veut déjà dire « voir les
différences » dans trois invites, et une lettre qui signifie deux choses
est pire qu'une lettre qui ne signifie rien. « t » était libre, et il est
le même partout — une seule chaîne porte les deux raccourcis.
Regarder n'est pas répondre : la même question revient ensuite.
Ce qui manquait, c'étaient les données. Les échecs et les verdicts
d'outils sont désormais retenus avec l'étape où ils se sont produits.
Assisted-by: Claude Opus 5
2026-08-19 03:53:56 -04:00
" Migration state " : {
" fr " : " État de la migration " ,
" en " : " Migration state " ,
} ,
" started " : {
" fr " : " démarrée " ,
" en " : " started " ,
} ,
" last written " : {
" fr " : " dernière écriture " ,
" en " : " last written " ,
} ,
" current step " : {
" fr " : " étape courante " ,
" en " : " current step " ,
} ,
" Test results " : {
" fr " : " Résultat des tests " ,
" en " : " Test results " ,
} ,
" No tool has run yet. " : {
" fr " : " Aucun outil n ' a encore tourné. " ,
" en " : " No tool has run yet. " ,
} ,
" Commands that failed " : {
" fr " : " Commandes en échec " ,
" en " : " Commands that failed " ,
} ,
" What was done, step by step " : {
" fr " : " Ce qui a été fait, étape par étape " ,
" en " : " What was done, step by step " ,
} ,
" runs " : {
" fr " : " passages " ,
" en " : " runs " ,
} ,
" nothing to report " : {
" fr " : " rien à signaler " ,
" en " : " nothing to report " ,
} ,
" findings to look at " : {
" fr " : " des trouvailles à regarder " ,
" en " : " findings to look at " ,
} ,
" the tool itself failed " : {
" fr " : " l ' outil lui-même a échoué " ,
" en " : " the tool itself failed " ,
} ,
" unknown result " : {
" fr " : " résultat inconnu " ,
" en " : " unknown result " ,
} ,
" before the first step " : {
" fr " : " avant la première étape " ,
" en " : " before the first step " ,
} ,
" No migration in progress. " : {
" fr " : " Aucune migration en cours. " ,
" en " : " No migration in progress. " ,
} ,
" show the migration state " : {
" fr " : " afficher l ' état de la migration " ,
" en " : " show the migration state " ,
} ,
" Nothing to show yet. " : {
" fr " : " Rien à montrer pour l ' instant. " ,
" en " : " Nothing to show yet. " ,
} ,
" (b = go back a step, t = show the migration state) " : {
" fr " : " (b = revenir à une étape précédente, "
" t = afficher l ' état de la migration) " ,
" en " : " (b = go back a step, t = show the migration state) " ,
} ,
[ADD] migration: reach the state screen from the statistics too
The statistics screen answers « what was removed, and why ». The state
screen answers « where are we, and what broke ». Two neighbouring
questions asked at the same moment: separating them by a menu entry
rather than by two commands to remember is what makes them usable.
Same letter as the prompts. One that changes meaning from one screen to
the next is not learned, it is looked up — and nobody looks it up.
Adding it surfaced two ways that screen could not survive auto-run. The
interface question is the FIRST one asked after auto-run is switched on,
and it was a bare input(): an unattended migration stopped there before
it had begun. The statistics menu was one too. And the state screen
writes the progression before reading it — but the statistics screen runs
before the progression is even loaded, so that write failed on an
attribute that does not exist yet.
--- FR ---
[ADD] migration : atteindre l'état depuis les statistiques aussi
L'écran de statistiques répond « qu'a-t-on supprimé, et pourquoi ». Celui
de l'état répond « où en est-on, et qu'est-ce qui a cassé ». Deux
questions voisines, posées au même moment : les séparer par une entrée de
menu plutôt que par deux commandes à retenir est ce qui les rend
utilisables.
La même lettre que dans les invites. Une lettre qui change de sens d'un
écran à l'autre ne s'apprend pas, elle se cherche — et personne ne la
cherche.
L'ajout a révélé deux façons dont cet écran ne survivait pas au mode
auto : la question d'interface, PREMIÈRE posée après l'activation, était
un input() nu, et le menu des statistiques aussi. Et l'écran d'état écrit
la progression avant de la lire, alors que les statistiques s'ouvrent
avant même qu'elle ne soit chargée.
Assisted-by: Claude Opus 5
2026-08-19 04:05:03 -04:00
" Migration state (full screen) " : {
" fr " : " État de la migration (plein écran) " ,
" en " : " Migration state (full screen) " ,
} ,
[FIX] migration: Enter exports the backup at the end
After several hours of migration the archive is what one wanted anyway,
and the prompt asked for it with an empty default: pressing Enter — or
letting auto-run answer — walked past the only artifact of the whole run.
The three-way answer moved into a function of its own, because the third
way is the one that traps. « n » refuses, « y » or Enter takes the
timestamped name, and EVERYTHING ELSE is a filename. So « non.zip » is a
file, not a refusal: comparing the whole answer rather than its first
letter is what keeps it one.
--- FR ---
[FIX] migration : Entrée exporte la sauvegarde à la fin
Au bout de plusieurs heures de migration, l'archive est ce qu'on voulait
de toute façon, et l'invite la proposait avec un défaut vide : appuyer sur
Entrée — ou laisser l'auto-exécution répondre — passait à côté du seul
artefact de toute la course.
La réponse à trois sens est devenue une fonction à part, car c'est le
troisième qui piège. « n » refuse, « y » ou Entrée prend le nom horodaté,
et TOUT LE RESTE est un nom de fichier. « non.zip » est donc un fichier et
non un refus : comparer la réponse entière plutôt que sa première lettre
est ce qui le garantit.
Assisted-by: Claude Opus 5
2026-08-19 04:20:13 -04:00
" Export a backup? " : {
" fr " : " Exporter une sauvegarde ? " ,
" en " : " Export a backup? " ,
} ,
" or type filename.zip " : {
" fr " : " ou tapez nomfichier.zip " ,
" en " : " or type filename.zip " ,
} ,
[FIX] smoke: judge the back office AFTER the repair, and say why it failed
No timeout was hit and no test was skipped. The back office failed for the
same reason the site did: the COW copy of website.submenu breaks
/web/login exactly as it breaks the public pages — both go through the
same layout. The pass simply ran BEFORE the reset, and unlike the URLs it
was never looked at again. It is now, on the same second server.
« Session expired » described the consequence and hid the cause. The login
page returned 500 and nothing said so, so one looks at the password. The
status of that page, and a missing CSRF token, are now named.
Diagnosing this, I started Odoo 18 on a 17 database and got 500 on all
thirty-seven URLs and on /web/login: a report that says the site is
entirely broken when nothing is. database_cleanup already refused that;
the smoke tools now share the same guard rather than reimplementing it.
--- FR ---
[FIX] smoke : juger le back-office APRÈS la réparation, et dire pourquoi
Aucun délai n'a été atteint et aucun test n'a été sauté. Le back-office a
échoué pour la raison même qui cassait le site : la copie COW de
website.submenu casse /web/login comme elle casse les pages publiques —
les deux passent par le même gabarit. La passe tournait avant la
réinitialisation et, contrairement aux URL, n'était jamais revue.
« Session expired » décrivait la conséquence et cachait la cause : la page
de connexion rendait 500 sans que rien ne le dise, et l'on cherchait du
côté du mot de passe.
En diagnostiquant, j'ai lancé un Odoo 18 sur une base 17 : 500 partout, un
rapport qui déclare le site entièrement cassé quand rien ne l'est.
database_cleanup refusait déjà cela ; les outils de fumée partagent
désormais sa garde au lieu d'en écrire une seconde.
Assisted-by: Claude Opus 5
2026-08-19 04:53:43 -04:00
" The login page itself failed " : {
" fr " : " La page de connexion elle-même a échoué " ,
" en " : " The login page itself failed " ,
} ,
" No CSRF token on the login page " : {
" fr " : " Aucun jeton CSRF sur la page de connexion " ,
" en " : " No CSRF token on the login page " ,
} ,
" after the reset " : {
" fr " : " après la réinitialisation " ,
" en " : " after the reset " ,
} ,
" The differential detection found nothing; resetting " : {
" fr " : " La détection différentielle n ' a rien trouvé ; réinitialisation " ,
" en " : " The differential detection found nothing; resetting " ,
} ,
" the requested key(s) anyway. " : {
" fr " : " de la ou des clés demandées malgré tout. " ,
" en " : " the requested key(s) anyway. " ,
} ,
[ADD] migration state: l, p, r and the two resize keys
Four keys, and one bug behind them. « l » separates the commands from the
server log: the first says what was LAUNCHED, the second what it
ANSWERED, and an update writing tens of thousands of lines buries three
commands. « p » hides the summary panel, « - » and « + » move the split,
« r » re-reads the disk — the migration writes while one watches, and
closing the screen to see the next bump is what one ended up doing.
The « missing logs » had two causes, both real. A tail was shown without
saying it was one; it now names how many lines are hidden. And the first
two steps run before the database is named, so their logs landed under
« sans-nom » — outside the migration they belong to, invisible from the
screen. They are brought back the moment the name is known, appended
never overwritten.
The step 4 bug was already fixed: its log held all six bumps because only
print_step opened one, and the bump loop never calls it.
--- FR ---
[ADD] état de migration : l, p, r et les deux touches de taille
Quatre touches, et un défaut derrière. « l » sépare les commandes du
journal : la première dit ce qui a été LANCÉ, le second ce que cela a
RÉPONDU, et une mise à jour qui écrit des dizaines de milliers de lignes
enterre trois commandes. « p » cache le panneau de résumé, « - » et « + »
déplacent la séparation, « r » relit le disque — la migration écrit
pendant qu'on regarde.
Les « logs manquants » avaient deux causes. On montrait une fin sans dire
que c'en était une ; le nombre de lignes cachées est désormais nommé. Et
les deux premières étapes tournent avant que la base ne soit nommée :
leurs journaux atterrissaient sous « sans-nom », hors de la migration à
laquelle ils appartiennent. Ils la rejoignent dès que le nom est connu.
Assisted-by: Claude Opus 5
2026-08-19 06:23:20 -04:00
" lines " : {
" fr " : " lignes " ,
" en " : " lines " ,
} ,
" press l to show " : {
" fr " : " l pour afficher " ,
" en " : " press l to show " ,
} ,
" last " : {
" fr " : " dernières " ,
" en " : " last " ,
} ,
" Refresh " : {
" fr " : " Rafraîchir " ,
" en " : " Refresh " ,
} ,
" Logs " : {
" fr " : " Journaux " ,
" en " : " Logs " ,
} ,
" Panel " : {
" fr " : " Panneau " ,
" en " : " Panel " ,
} ,
" Wider " : {
" fr " : " Élargir " ,
" en " : " Wider " ,
} ,
" Narrower " : {
" fr " : " Rétrécir " ,
" en " : " Narrower " ,
} ,
2026-08-19 06:37:14 -04:00
" finished " : {
" fr " : " terminée " ,
" en " : " finished " ,
} ,
" duration " : {
" fr " : " durée " ,
" en " : " duration " ,
} ,
[ADD] migration: announce the countdown, and cycle three panel states
Every prompt now carries « ⏱5s » before the question. One that looks like
it waits forever does not invite you to step away, which is the whole
point of auto-run. The same clock reports the answer afterwards: it
announces the wait, then says what it decided.
Enter on the auto-run question already meant no; it now says so, with an
explicit default instead of a deduction. And the flag is cleared before
asking — an ERPLIBRE_AUTO_EXECUTE inherited from the shell would have
answered yes on someone's behalf, and that answer commits every later one.
« p » cycles three states rather than toggling one: a plain toggle freed
four lines, while what takes the room is the left COLUMN — on a server log
every character of width counts. All → no summary → detail only, and back.
The state is named in the sub-title, which stays visible in all three: a
panel that vanishes without a word reads as a broken screen.
--- FR ---
[ADD] migration : annoncer le compte à rebours, et trois états de panneaux
Chaque invite porte désormais « ⏱5s » devant la question. Une invite qui a
l'air d'attendre indéfiniment n'invite pas à s'absenter, et c'est pourtant
tout l'intérêt du mode auto. Le même chronomètre rend compte ensuite.
Entrée sur la question d'auto-exécution voulait déjà dire non ; elle le
DIT maintenant, par un défaut explicite plutôt qu'une déduction. Et le
drapeau est effacé avant de demander : une variable héritée du shell
aurait répondu « oui » à la place de quelqu'un.
« p » enchaîne trois états au lieu d'en basculer un : ce qui prend la
place, c'est la colonne de gauche. Tout → sans résumé → détail seul, et
l'on revient. L'état est nommé dans le sous-titre.
Assisted-by: Claude Opus 5
2026-08-19 06:57:19 -04:00
" Panels " : {
" fr " : " Panneaux " ,
" en " : " Panels " ,
} ,
" summary + list + detail " : {
" fr " : " résumé + liste + détail " ,
" en " : " summary + list + detail " ,
} ,
" list + detail " : {
" fr " : " liste + détail " ,
" en " : " list + detail " ,
} ,
" detail only " : {
" fr " : " détail seul " ,
" en " : " detail only " ,
} ,
[ADD] analyse: what a migration gained and lost, step by step
A migration leaves one database per step and they all still exist, so the
tool compares them side by side rather than replaying anything. Six Odoo
starts would cost an hour, write to the databases and need the checkout
switched at each step; the same inspection in SQL takes under half a
second per database and touches nothing. Measured on a real migration:
seven databases surveyed in 3.8 seconds.
It reports what each step gained and lost — modules, models, views, menus,
attachments — and ends on the comparison of the ENDS, which is not the sum
of the steps: a module dropped at 15 and put back at 17 lost nothing, and
adding the steps would count it twice.
The signal that matters is rows. A module removed is visible; a table
going from four thousand rows to zero is visible nowhere.
Two rename heuristics were tried and rejected on real data before the one
that holds. Row count alone paired an account tag table with dms_directory
— both had seven rows. A shared five-letter word paired two cleanup
tables. Overall name similarity separates them. And a renamed table stays
in the loss list, annotated: removing it was the real danger, since a
wrong pairing would have hidden a genuine loss.
--- FR ---
[ADD] analyse : ce qu'une migration a gagné et perdu, palier par palier
Une migration laisse une base par palier et elles existent toutes encore :
l'outil les compare côte à côte plutôt que de rejouer quoi que ce soit.
Six démarrages d'Odoo coûteraient une heure et écriraient dans les bases ;
la même inspection en SQL prend moins d'une demi-seconde par base. Mesuré
sur une vraie migration : sept bases inspectées en 3,8 secondes.
Le bilan final compare les EXTRÉMITÉS, ce qui n'est pas la somme des
paliers : un module retiré en 15 puis remis en 17 n'a rien perdu.
Le signal qui compte, ce sont les lignes. Un module en moins se voit ; une
table qui passe de quatre mille lignes à zéro ne se voit nulle part.
Deux rapprochements de renommage ont été essayés et rejetés sur des
données réelles. Et une table renommée reste dans la liste des pertes,
annotée : l'en retirer était le vrai danger.
Assisted-by: Claude Opus 5
2026-08-19 08:21:12 -04:00
" errors in the log " : {
" fr " : " erreurs dans le journal " ,
" en " : " errors in the log " ,
} ,
" Migration quality, step by step " : {
" fr " : " Qualité de la migration, palier par palier " ,
" en " : " Migration quality, step by step " ,
} ,
" database not found " : {
" fr " : " base introuvable " ,
" en " : " database not found " ,
} ,
" attachment files missing from the filestore " : {
" fr " : " fichiers de pièces jointes absents du filestore " ,
" en " : " attachment files missing from the filestore " ,
} ,
" From start to finish " : {
" fr " : " Du départ à l ' arrivée " ,
" en " : " From start to finish " ,
} ,
" not comparable: a database is missing " : {
" fr " : " non comparable : une base manque " ,
" en " : " not comparable: a database is missing " ,
} ,
" probable table rename(s) " : {
" fr " : " renommage(s) de table probable(s) " ,
" en " : " probable table rename(s) " ,
} ,
" table(s) lost rows " : {
" fr " : " table(s) ont perdu des lignes " ,
" en " : " table(s) lost rows " ,
} ,
" attachments " : {
" fr " : " pièces jointes " ,
" en " : " attachments " ,
} ,
" probably renamed to " : {
" fr " : " probablement renommée en " ,
" en " : " probably renamed to " ,
} ,
" steps " : {
" fr " : " paliers " ,
" en " : " steps " ,
} ,
" Migration " : {
[FIX] todo analyse: give the migration entry its icon
An entry with no icon, in a menu where every other one has one, reads as
unfinished. The section gets 🚚 and the entry 📐 — the same symbol the
quality report already wears in its own title, because it is the same
thing.
The spacing follows a convention this menu keeps without stating it: an
icon of « neutral » width — 🗄, 🖼 — occupies one column in a terminal
and takes TWO spaces to line up, while a wide one — 📏, 📐 — takes one.
Getting it wrong shifts the line, and nothing says so until it is under
your eyes. A test now states it.
--- FR ---
[FIX] todo analyse : donner son icône à l'entrée de migration
Une entrée sans icône, dans un menu où toutes les autres en ont une, se
lit comme une entrée inachevée. La section reçoit 🚚 et l'entrée 📐 — le
symbole que le rapport de qualité porte déjà dans son propre titre,
puisque c'est la même chose.
L'espacement suit une convention que ce menu tient sans la dire : une
icône de largeur « neutre » — 🗄, 🖼 — s'affiche sur une colonne dans un
terminal et prend DEUX espaces pour s'aligner, une icône large — 📏, 📐 —
un seul. Se tromper décale la ligne, et rien ne le signale avant de
l'avoir sous les yeux. Un test l'énonce désormais.
Assisted-by: Claude Opus 5
2026-08-20 02:08:15 -04:00
" fr " : " 🚚 Migration " ,
" en " : " 🚚 Migration " ,
[ADD] analyse: what a migration gained and lost, step by step
A migration leaves one database per step and they all still exist, so the
tool compares them side by side rather than replaying anything. Six Odoo
starts would cost an hour, write to the databases and need the checkout
switched at each step; the same inspection in SQL takes under half a
second per database and touches nothing. Measured on a real migration:
seven databases surveyed in 3.8 seconds.
It reports what each step gained and lost — modules, models, views, menus,
attachments — and ends on the comparison of the ENDS, which is not the sum
of the steps: a module dropped at 15 and put back at 17 lost nothing, and
adding the steps would count it twice.
The signal that matters is rows. A module removed is visible; a table
going from four thousand rows to zero is visible nowhere.
Two rename heuristics were tried and rejected on real data before the one
that holds. Row count alone paired an account tag table with dms_directory
— both had seven rows. A shared five-letter word paired two cleanup
tables. Overall name similarity separates them. And a renamed table stays
in the loss list, annotated: removing it was the real danger, since a
wrong pairing would have hidden a genuine loss.
--- FR ---
[ADD] analyse : ce qu'une migration a gagné et perdu, palier par palier
Une migration laisse une base par palier et elles existent toutes encore :
l'outil les compare côte à côte plutôt que de rejouer quoi que ce soit.
Six démarrages d'Odoo coûteraient une heure et écriraient dans les bases ;
la même inspection en SQL prend moins d'une demi-seconde par base. Mesuré
sur une vraie migration : sept bases inspectées en 3,8 secondes.
Le bilan final compare les EXTRÉMITÉS, ce qui n'est pas la somme des
paliers : un module retiré en 15 puis remis en 17 n'a rien perdu.
Le signal qui compte, ce sont les lignes. Un module en moins se voit ; une
table qui passe de quatre mille lignes à zéro ne se voit nulle part.
Deux rapprochements de renommage ont été essayés et rejetés sur des
données réelles. Et une table renommée reste dans la liste des pertes,
annotée : l'en retirer était le vrai danger.
Assisted-by: Claude Opus 5
2026-08-19 08:21:12 -04:00
} ,
" Quality of a migration, step by step " : {
[FIX] todo analyse: give the migration entry its icon
An entry with no icon, in a menu where every other one has one, reads as
unfinished. The section gets 🚚 and the entry 📐 — the same symbol the
quality report already wears in its own title, because it is the same
thing.
The spacing follows a convention this menu keeps without stating it: an
icon of « neutral » width — 🗄, 🖼 — occupies one column in a terminal
and takes TWO spaces to line up, while a wide one — 📏, 📐 — takes one.
Getting it wrong shifts the line, and nothing says so until it is under
your eyes. A test now states it.
--- FR ---
[FIX] todo analyse : donner son icône à l'entrée de migration
Une entrée sans icône, dans un menu où toutes les autres en ont une, se
lit comme une entrée inachevée. La section reçoit 🚚 et l'entrée 📐 — le
symbole que le rapport de qualité porte déjà dans son propre titre,
puisque c'est la même chose.
L'espacement suit une convention que ce menu tient sans la dire : une
icône de largeur « neutre » — 🗄, 🖼 — s'affiche sur une colonne dans un
terminal et prend DEUX espaces pour s'aligner, une icône large — 📏, 📐 —
un seul. Se tromper décale la ligne, et rien ne le signale avant de
l'avoir sous les yeux. Un test l'énonce désormais.
Assisted-by: Claude Opus 5
2026-08-20 02:08:15 -04:00
" fr " : " 📐 Qualité d ' une migration, palier par palier " ,
" en " : " 📐 Quality of a migration, step by step " ,
[ADD] analyse: what a migration gained and lost, step by step
A migration leaves one database per step and they all still exist, so the
tool compares them side by side rather than replaying anything. Six Odoo
starts would cost an hour, write to the databases and need the checkout
switched at each step; the same inspection in SQL takes under half a
second per database and touches nothing. Measured on a real migration:
seven databases surveyed in 3.8 seconds.
It reports what each step gained and lost — modules, models, views, menus,
attachments — and ends on the comparison of the ENDS, which is not the sum
of the steps: a module dropped at 15 and put back at 17 lost nothing, and
adding the steps would count it twice.
The signal that matters is rows. A module removed is visible; a table
going from four thousand rows to zero is visible nowhere.
Two rename heuristics were tried and rejected on real data before the one
that holds. Row count alone paired an account tag table with dms_directory
— both had seven rows. A shared five-letter word paired two cleanup
tables. Overall name similarity separates them. And a renamed table stays
in the loss list, annotated: removing it was the real danger, since a
wrong pairing would have hidden a genuine loss.
--- FR ---
[ADD] analyse : ce qu'une migration a gagné et perdu, palier par palier
Une migration laisse une base par palier et elles existent toutes encore :
l'outil les compare côte à côte plutôt que de rejouer quoi que ce soit.
Six démarrages d'Odoo coûteraient une heure et écriraient dans les bases ;
la même inspection en SQL prend moins d'une demi-seconde par base. Mesuré
sur une vraie migration : sept bases inspectées en 3,8 secondes.
Le bilan final compare les EXTRÉMITÉS, ce qui n'est pas la somme des
paliers : un module retiré en 15 puis remis en 17 n'a rien perdu.
Le signal qui compte, ce sont les lignes. Un module en moins se voit ; une
table qui passe de quatre mille lignes à zéro ne se voit nulle part.
Deux rapprochements de renommage ont été essayés et rejetés sur des
données réelles. Et une table renommée reste dans la liste des pertes,
annotée : l'en retirer était le vrai danger.
Assisted-by: Claude Opus 5
2026-08-19 08:21:12 -04:00
} ,
" Quality " : {
" fr " : " Qualité " ,
" en " : " Quality " ,
} ,
[FIX] migration state: open the quality screen in its own process
Pressing « k » crashed with « asyncio.run() cannot be called from a
running event loop ». `suspend()` hands the terminal over but does NOT
stop the loop, and `app.run()` calls `asyncio.run()` underneath. A
subprocess has its own loop and paints the terminal it was given.
My test asserted that suspend() was CALLED, and that structure was
correct — the launch inside it was not. Structure is not behaviour, and
the test that would have caught this is the one that presses the key.
It exists now, and a guard refuses the nested start with a sentence
instead of forty lines of traceback.
The screen module also lost its shebang: it has no main, so the line
claimed something that was not true, and the permission rule was right
to say so.
--- FR ---
[FIX] état de migration : ouvrir l'écran de qualité dans son processus
« k » plantait sur « asyncio.run() cannot be called from a running event
loop ». `suspend()` rend le terminal mais n'arrête PAS la boucle, et
`app.run()` appelle `asyncio.run()` en dessous. Un sous-processus a sa
propre boucle.
Mon test vérifiait que suspend() était APPELÉ — et cette structure était
juste ; c'est le lancement à l'intérieur qui ne l'était pas. La structure
n'est pas le comportement, et le test qui aurait attrapé cela est celui
qui presse la touche. Il existe désormais, et un garde-fou refuse le
lancement imbriqué par une phrase plutôt que par quarante lignes de trace.
Le module d'écran perd aussi son shebang : sans `main`, cette ligne
annonçait une intention qui n'existait pas.
Assisted-by: Claude Opus 5
2026-08-19 10:52:49 -04:00
" Already inside a running screen: open it in its own " : {
" fr " : " Déjà dans un écran en cours : l ' ouvrir dans son propre " ,
" en " : " Already inside a running screen: open it in its own " ,
} ,
" process instead. " : {
" fr " : " processus. " ,
" en " : " process instead. " ,
} ,
[ADD] migration quality: name the missing files, and show every delta
« 254 attachment files missing » does not say which. « m » now lists
them, grouped by model AND FIELD — the field is the most useful thing in
the row, because it says which field lost its image. On a real migration
it splits into 234 res.country/image flags, 13 payment.provider logos,
and a handful of scattered attachments including one 255 kB event photo:
the first two are module data a reinstall restores, the last is not, and
the raw list put them on the same footing.
The metadata is read ONLY when asked. One more query per database would
lengthen a survey that runs in four seconds, for something one looks at
by asking for it.
And every figure of a step now carries its change against the previous
one. « 2283 views » is a number; « +61 » is information. The first step
gets none: inventing a zero there would claim a comparison that does not
exist.
--- FR ---
[ADD] qualité de migration : nommer les fichiers absents, et montrer les écarts
« 254 fichiers de pièces jointes absents » ne dit pas lesquels. « m » en
donne la liste, groupée par modèle ET PAR CHAMP — le champ est le
renseignement le plus utile de la ligne, car il dit lequel a perdu son
image. Sur une vraie migration : 234 drapeaux res.country/image, 13 logos
de fournisseurs de paiement, et une poignée de pièces jointes éparses dont
une photo d'événement de 255 ko. Les deux premiers groupes sont des
données de module qu'une réinstallation restaure, le dernier non.
Les métadonnées ne sont lues QU'À LA DEMANDE : une requête de plus par
base allongerait un parcours qui tient en quatre secondes.
Et chaque chiffre d'un palier porte son écart avec le précédent. « 2283
vues » est un nombre, « +61 » est une information. Le premier palier n'en
a pas : y inventer un zéro annoncerait une comparaison qui n'existe pas.
Assisted-by: Claude Opus 5
2026-08-20 01:52:35 -04:00
" Missing files " : {
" fr " : " Fichiers absents " ,
" en " : " Missing files " ,
} ,
" every attachment file is present " : {
" fr " : " tous les fichiers de pièces jointes sont présents " ,
" en " : " every attachment file is present " ,
} ,
" Could not read their metadata. " : {
" fr " : " Impossible de lire leurs métadonnées. " ,
" en " : " Could not read their metadata. " ,
} ,
" by model and field " : {
" fr " : " par modèle et par champ " ,
" en " : " by model and field " ,
} ,
" one by one " : {
" fr " : " un par un " ,
" en " : " one by one " ,
} ,
" press m to list them " : {
" fr " : " m pour en voir la liste " ,
" en " : " press m to list them " ,
} ,
" Pick a step to see its missing files. " : {
" fr " : " Choisissez un palier pour voir ses fichiers absents. " ,
" en " : " Pick a step to see its missing files. " ,
} ,
[ADD] migration quality: tell Odoo's own redesigns from real losses
« 81 tables lost rows » buried the questions worth asking. The largest of
them — ir_translation, 32 984 rows — is a redesign Odoo made in 16, when
translations moved into jsonb columns. Putting redesigns and real losses
on the same footing is the surest way not to see the second kind.
A map now names what Odoo moves or retires, and the report splits the
list in two: unexplained first, then what Odoo did on purpose. On a real
12 → 18 the split is 57 and 14, and the partition is exact — an explained
loss is annotated, never removed, because removing it was the one thing
that could hide a real one.
Every entry was VERIFIED on that migration by counting both sides, not
written from memory: crm_lead_tag 6 → crm_tag +6, mail_channel 26 →
discuss_channel +26, account_invoice 651 → account_move +441. A map
written from memory would explain losses that are not losses.
And a merge whose target gained nothing says so: the explanation does not
hold, and that is exactly what the map was meant to surface.
--- FR ---
[ADD] qualité de migration : distinguer les refontes d'Odoo des vraies pertes
« 81 tables ont perdu des lignes » enterrait les questions qui valaient la
peine. La plus grosse — ir_translation, 32 984 lignes — est une refonte
d'Odoo 16, quand les traductions sont passées en colonnes jsonb.
Une carte nomme désormais ce qu'Odoo déplace ou retire, et le rapport
sépare la liste en deux : l'inexpliqué d'abord, puis ce qu'Odoo a fait
exprès. Sur une vraie 12 → 18 : 57 et 14, et la partition est exacte —
une perte expliquée est annotée, jamais retirée.
Chaque entrée a été VÉRIFIÉE sur cette migration en comptant les deux
côtés : crm_lead_tag 6 → crm_tag +6, mail_channel 26 → discuss_channel
+26, account_invoice 651 → account_move +441.
Et une fusion dont la table d'accueil n'a rien reçu le dit : l'explication
ne tient pas, et c'est précisément ce que la carte devait révéler.
Assisted-by: Claude Opus 5
2026-08-20 02:42:18 -04:00
" table(s) lost rows, unexplained " : {
" fr " : " table(s) ont perdu des lignes, sans explication " ,
" en " : " table(s) lost rows, unexplained " ,
} ,
[ADD] migration quality: explain ir_property and tracking values
ir_property never empties, it GROWS — 211 rows in 12, 457 in 17 — then
vanishes in 18. Its fields are jsonb columns there, verified on
res_partner.property_payment_term_id.
mail_tracking_value is a REAL loss, not a rename: `field` goes from
varchar to a foreign key in 14, and what would not resolve was deleted.
1528 of the 2336 belonged to the removed agile module; 706 more survived
under a new name. New kind "pruned" so the report cannot call a living
table retired.
--- FR ---
ir_property ne se vide jamais, elle GROSSIT — 211 lignes en 12, 457 en
17 — puis disparaît en 18. Ses champs y sont des colonnes jsonb, vérifié
sur res_partner.property_payment_term_id.
mail_tracking_value est une VRAIE perte, pas un renommage : `field`
passe de varchar à clé étrangère en 14, et ce qui ne se résolvait pas a
été supprimé. 1528 des 2336 venaient du module agile retiré ; 706 autres
ont survécu sous un autre nom. Nouveau genre « pruned » pour que le
rapport ne déclare pas retirée une table bien vivante.
Assisted-by: Claude Opus 5
2026-08-21 03:31:28 -04:00
" table(s) explained by an Odoo change " : {
" fr " : " table(s) expliquées par un changement d ' Odoo " ,
" en " : " table(s) explained by an Odoo change " ,
} ,
2026-08-21 05:00:38 -04:00
" Cannot read the database: " : {
" fr " : " Lecture impossible de la base : " ,
" en " : " Cannot read the database: " ,
} ,
" Cannot read the package file: " : {
" fr " : " Lecture impossible du fichier de packages : " ,
" en " : " Cannot read the package file: " ,
} ,
" Modules missing from the default package " : {
" fr " : " 📦 Modules absents du package par défaut " ,
" en " : " 📦 Modules missing from the default package " ,
} ,
[ADD] analyse: qui dépend de qui, à l'écran
Le menu Analyse disait quels modules manquent, jamais qui dépend de qui.
« Puis-je retirer celui-ci » se réglait donc à la main : au palier
17 → 18, il a fallu écrire la requête pour savoir si web_responsive
pouvait partir.
L'écran liste les modules et « d » parcourt quatre relations : ce dont il
dépend, ce qui en dépend, tout ce qu'il entraîne, tout ce qui tombe avec
lui. « f » filtre, « / » cherche — une base en porte trois mille. La
réponse qui compte est écrite en toutes lettres : deux dépendants
déclarés dont zéro installé, c'est un retrait sans danger.
--- EN ---
The Analyse menu told which modules were missing, never which depends on
which. « Can I remove this one » was therefore answered by hand: at the
17 → 18 step we had to write the query to learn whether web_responsive
could go.
The screen lists the modules and « d » walks four relations: what it
needs, what needs it, everything it pulls in, everything that falls with
it. « f » filters, « / » searches — a database holds three thousand of
them. The answer that matters is spelled out: two declared dependents of
which zero installed means removal is safe.
Assisted-by: Claude Opus 5
2026-08-23 03:46:08 -04:00
" Dependencies between modules " : {
" fr " : " 🔗 Dépendances entre les modules " ,
" en " : " 🔗 Dependencies between modules " ,
} ,
[ADD] migration: réparer les copies de site qui ne savent plus se rendre
`check_cow_views` cherche la rupture qui ARRÊTE la migration : la forme
que l'arch doit avoir change. Deux autres la laissent finir sans un mot.
Un ancrage manquant : une vue héritière fait un xpath sur `//t[@t-set=…]`
que le module a gagné en chemin ; la copie, page d'utilisateur, n'est
jamais réécrite. Un t-call pendant : le gabarit appelé n'existe plus dans
la cible. Mesuré sur une 12 → 18 réelle : /contact rendait 500 depuis le
palier 14 → 15, quatre paliers de silence, et le test de fumée le voyait
sans savoir le nommer.
Le premier se répare depuis la vue module, en gardant la page telle que
son auteur l'a écrite. Le second se retire, et l'humain décide.
--- EN ---
`check_cow_views` looks for the break that STOPS the migration: the shape
the arch must have changes. Two others let it finish without a word.
A missing anchor: a child view xpaths on `//t[@t-set=…]` that the module
gained along the way; the copy, a user page, is never rewritten. A
dangling t-call: the template called no longer exists in the target.
Measured on a real 12 → 18: /contact returned 500 since the 14 → 15 step,
four steps of silence, and the smoke test saw it without naming it.
The first is repaired from the module view, keeping the page as its
author wrote it. The second is removed, and the human decides.
Assisted-by: Claude Opus 5
2026-08-24 02:53:24 -04:00
" Website copies that can no longer render " : {
" fr " : " Copies de site qui ne savent plus se rendre " ,
" en " : " Website copies that can no longer render " ,
} ,
" Every website copy still renders. " : {
" fr " : " Chaque copie de site sait encore se rendre. " ,
" en " : " Every website copy still renders. " ,
} ,
[ADD] migration: retirer les index qu'Odoo 17 a créés en double
`make_index_name` rend `{table}__{colonne}_index` depuis la 17 — deux
soulignés. Le nouvel index est créé, l'ancien reste. Mesuré sur une
chaîne 12 → 18 : 1 paire en 12, 3 en 16, 370 en 17, 365 en 18. Inerte à
la lecture, coûteux à l'écriture — deux arbres B entretenus à chaque
INSERT — et c'est le seul défaut de la famille qui empire tout seul.
L'outil s'abstient plus qu'il ne supprime : contrainte, clé primaire,
index partiel ou calculé, méthode ou classe d'opérateurs différentes,
convention non reconnaissable. Sur la base réelle, 367 sûrs sur 376.
Éprouvé sur une copie : 364 retirés, 10 Mo libérés, les 6301 contraintes
identiques au nom près, Odoo charge sans une ligne de journal, et un
« -u all » complet n'en recrée aucun.
--- EN ---
`make_index_name` returns `{table}__{column}_index` since 17 — two
underscores. The new index is created, the old one stays. Measured on a
12 → 18 chain: 1 pair in 12, 3 in 16, 370 in 17, 365 in 18. Inert on
read, costly on write — two B-trees maintained per INSERT — and the only
defect of this family that worsens on its own.
The tool abstains more than it drops: constraint, primary key, partial or
computed index, differing access method or operator class, unrecognisable
convention. On the real database, 367 safe out of 376.
Proven on a copy: 364 dropped, 10 MB freed, the 6301 constraints
identical name for name, Odoo loads without a single log line, and a full
« -u all » recreates none of them.
Assisted-by: Claude Opus 5
2026-08-24 04:02:27 -04:00
" No duplicate index. " : {
" fr " : " Aucun index en double. " ,
" en " : " No duplicate index. " ,
} ,
[FIX] analyse: dire CE QUI est parti avec une pièce jointe
« 409 pièces jointes perdues » au palier 17 → 18 n'en recouvrait presque
aucune. Sur les 516 lignes parties, 452 avaient perdu leur CHAMP PORTEUR
aux paliers 13 et 14 : elles étaient déjà illisibles — Odoo lève un
KeyError en les contrôlant. Ce ne sont pas des données, ce sont des
débris, et la 18 les ramasse.
On ne DÉCLARE pas cela dans SEMANTIC_MAP. Cette carte nomme une TABLE, et
la cause n'est pas la table, ce sont ces lignes-là ; déclarée, elle
rangerait toute perte future de ir_attachment sous « changement d'Odoo »,
cinq mille factures comprises. On la DÉDUIT, ligne par ligne, dans le
même vocabulaire.
Reste 64 lignes en rouge au palier 18 : leur champ est toujours là. Elles
demandent un contrôle d'existence d'enregistrement que je n'ai pas
mesuré, donc je ne l'affirme pas.
--- EN ---
« 409 attachments lost » at the 17 → 18 step covered almost none. Of the
516 rows that went, 452 had lost their CARRYING FIELD at steps 13 and 14:
they were already unreadable — Odoo raises a KeyError checking them. Not
data, debris, and 18 sweeps them up.
We do NOT declare this in SEMANTIC_MAP. That map names a TABLE, and the
cause is not the table but those rows; declared, it would file every
future ir_attachment loss under « an Odoo change », five thousand
invoices included. We DEDUCE it, row by row, in the same vocabulary.
64 rows stay red at step 18: their field is still there. They need a
record-existence check I have not measured, so I do not claim it.
Assisted-by: Claude Opus 5
2026-08-24 06:08:17 -04:00
" attachments of fields and records already gone " : {
" fr " : " pièces jointes de champs et d ' enregistrements déjà disparus " ,
" en " : " attachments of fields and records already gone " ,
} ,
" their field was already gone before this step " : {
" fr " : " leur champ porteur avait déjà disparu avant ce palier " ,
" en " : " their field was already gone before this step " ,
} ,
" their field was removed at this step " : {
" fr " : " leur champ porteur a été retiré à ce palier " ,
" en " : " their field was removed at this step " ,
} ,
" their model left the database " : {
" fr " : " leur modèle a quitté la base " ,
" en " : " their model left the database " ,
} ,
" the field is STILL there — look at these " : {
" fr " : " le champ est TOUJOURS là — à regarder " ,
" en " : " the field is STILL there — look at these " ,
} ,
2026-08-24 05:57:06 -04:00
" out of " : {
" fr " : " sur " ,
" en " : " out of " ,
} ,
" Every witness is an administrator: a model only " : {
" fr " : " Tous les témoins sont administrateurs : un modèle que seuls les " ,
" en " : " Every witness is an administrator: a model only " ,
} ,
" admins can read passes this test. " : {
" fr " : " administrateurs peuvent lire passe ce test. " ,
" en " : " admins can read passes this test. " ,
} ,
" Reads ir_rule only — masking coded in Python escapes it. " : {
" fr " : " Ne lit qu ' ir_rule — un masquage codé en Python lui échappe. " ,
" en " : " Reads ir_rule only — masking coded in Python escapes it. " ,
} ,
[FIX] analyse: écarter les champs qui n'ont jamais porté de donnée
Le seau « NON déclarés par OpenUpgrade » comptait 565 champs au palier
16 → 17, dont 397 `__last_update` — un champ magique qu'Odoo 17 cesse
d'inscrire et qui n'a jamais eu de colonne. Un chiffre de tête qui fait
peur pour rien fait ignorer le rapport entier.
`store=false` le dit sans ambiguïté, et `id` s'y ajoute : il porte une
donnée, mais celle de la ligne, pas la sienne. Ils vont dans une
catégorie à part, en teinte calme, et NOMMÉE — « __last_update × 101
modèle(s) » explique à lui seul le gros chiffre.
Mesuré sur la chaîne 12 → 18 : le total passe de 1176 à 491, le pire
palier de 565 à 57. Les quatre vrais champs techniques perdus restent
visibles.
--- EN ---
The « NOT declared by OpenUpgrade » bucket held 565 fields at the 16 → 17
step, 397 of them `__last_update` — a magic field Odoo 17 stops
recording, which never had a column. A headline number that frightens
for nothing gets the whole report ignored.
`store=false` says so unambiguously, and `id` joins it: it carries data,
but the row's, not its own. They go to a separate bucket, in a quiet
tint, and NAMED — « __last_update × 101 model(s) » explains the big
number on its own.
Measured on the 12 → 18 chain: the total drops from 1176 to 491, the
worst step from 565 to 57. The four genuinely lost technical fields stay
visible.
Assisted-by: Claude Opus 5
2026-08-24 05:48:03 -04:00
" held no data of their own — nothing to lose " : {
" fr " : " sans donnée propre — rien à perdre " ,
" en " : " held no data of their own — nothing to lose " ,
} ,
[FIX] qemu : ne jamais proposer d'effacer un fichier en usage
Rapporté, et c'est le pire défaut possible ici. Après avoir renommé une VM
en « erplibre-ubuntu-2404-MIGRATION », le nettoyage offrait au « rm -f » son
disque de 63 Go, son seed et son nvram — les trois attachés à une VM EN
MARCHE. L'orphelinat se jugeait sur le NOM du fichier comparé aux noms de
domaines ; un renommage suffisait donc à condamner une VM de production.
L'autorité est désormais libvirt : ce qu'un domaine référence n'est pas
orphelin, quel que soit son nom, et l'écran nomme ce qu'il conserve et pour
qui. Un second contrôle, indépendant, épargne aussi ce qu'un processus tient
ouvert. L'effacement d'une VM demande de même ses fichiers à libvirt : par le
nom, il laissait les 63 Go derrière lui.
--- EN ---
Reported, and it is the worst possible defect here. After renaming a VM to
"erplibre-ubuntu-2404-MIGRATION", cleanup offered its 63 GB disk, its seed
and its nvram to "rm -f" — all three attached to a RUNNING VM. Orphanhood was
judged on the file NAME against domain names; a rename was therefore enough
to condemn a production VM.
Libvirt is now the authority: what a domain references is not an orphan,
whatever its name, and the screen names what it keeps and for whom. A second,
independent check also spares whatever a process holds open. Deleting a VM
likewise asks libvirt for its files: by name, it left the 63 GB behind.
Assisted-by: Claude Opus 5
2026-08-24 04:42:09 -04:00
" Configuration records a migration never recreates " : {
" fr " : " Réglages qu ' une migration ne recrée jamais " ,
" en " : " Configuration records a migration never recreates " ,
} ,
" Every default configuration record is there. " : {
" fr " : " Chaque réglage par défaut est en place. " ,
" en " : " Every default configuration record is there. " ,
} ,
" No pricelist, and the pricelist feature is on. " : {
" fr " : " Aucune liste de prix, alors que la fonctionnalité est active. " ,
" en " : " No pricelist, and the pricelist feature is on. " ,
} ,
" Every quotation opens without one: raw price, no rule. " : {
" fr " : " Tout devis s ' ouvre sans liste : prix brut, aucune règle. " ,
" en " : " Every quotation opens without one: raw price, no rule. " ,
} ,
" No bank reconciliation model, for " : {
" fr " : " Aucun modèle de rapprochement bancaire, pour " ,
" en " : " No bank reconciliation model, for " ,
} ,
" cash/bank journal(s). " : {
" fr " : " journal(aux) de trésorerie. " ,
" en " : " cash/bank journal(s). " ,
} ,
" Every statement line is matched by hand. " : {
" fr " : " Chaque ligne de relevé se lettre à la main. " ,
" en " : " Every statement line is matched by hand. " ,
} ,
" Use --apply to let Odoo recreate them. " : {
" fr " : " Utiliser --apply pour laisser Odoo les recréer. " ,
" en " : " Use --apply to let Odoo recreate them. " ,
} ,
" pricelists: " : {
" fr " : " listes de prix : " ,
" en " : " pricelists: " ,
} ,
" reconcile models: " : {
" fr " : " modèles de rapprochement : " ,
" en " : " reconcile models: " ,
} ,
" Odoo recreated them. " : {
" fr " : " Odoo les a recréés. " ,
" en " : " Odoo recreated them. " ,
} ,
" Still missing after the repair. " : {
" fr " : " Il en manque encore après la réparation : " ,
" en " : " Still missing after the repair. " ,
} ,
[ADD] migration: retirer les index qu'Odoo 17 a créés en double
`make_index_name` rend `{table}__{colonne}_index` depuis la 17 — deux
soulignés. Le nouvel index est créé, l'ancien reste. Mesuré sur une
chaîne 12 → 18 : 1 paire en 12, 3 en 16, 370 en 17, 365 en 18. Inerte à
la lecture, coûteux à l'écriture — deux arbres B entretenus à chaque
INSERT — et c'est le seul défaut de la famille qui empire tout seul.
L'outil s'abstient plus qu'il ne supprime : contrainte, clé primaire,
index partiel ou calculé, méthode ou classe d'opérateurs différentes,
convention non reconnaissable. Sur la base réelle, 367 sûrs sur 376.
Éprouvé sur une copie : 364 retirés, 10 Mo libérés, les 6301 contraintes
identiques au nom près, Odoo charge sans une ligne de journal, et un
« -u all » complet n'en recrée aucun.
--- EN ---
`make_index_name` returns `{table}__{column}_index` since 17 — two
underscores. The new index is created, the old one stays. Measured on a
12 → 18 chain: 1 pair in 12, 3 in 16, 370 in 17, 365 in 18. Inert on
read, costly on write — two B-trees maintained per INSERT — and the only
defect of this family that worsens on its own.
The tool abstains more than it drops: constraint, primary key, partial or
computed index, differing access method or operator class, unrecognisable
convention. On the real database, 367 safe out of 376.
Proven on a copy: 364 dropped, 10 MB freed, the 6301 constraints
identical name for name, Odoo loads without a single log line, and a full
« -u all » recreates none of them.
Assisted-by: Claude Opus 5
2026-08-24 04:02:27 -04:00
" duplicate index pair(s) " : {
" fr " : " paire(s) d ' index en double " ,
" en " : " duplicate index pair(s) " ,
} ,
" safe to drop " : {
" fr " : " sans risque à supprimer " ,
" en " : " safe to drop " ,
} ,
" drop " : {
" fr " : " supprimer " ,
" en " : " drop " ,
} ,
" backed by a constraint — left alone " : {
" fr " : " adossée(s) à une contrainte — laissée(s) en place " ,
" en " : " backed by a constraint — left alone " ,
} ,
" neither name follows the convention — your call " : {
" fr " : " sans convention reconnaissable — à vous de juger " ,
" en " : " neither name follows the convention — your call " ,
} ,
" Use --apply to drop them. " : {
" fr " : " Utiliser --apply pour les supprimer. " ,
" en " : " Use --apply to drop them. " ,
} ,
" Duplicate indexes dropped. " : {
" fr " : " Index en double supprimés. " ,
" en " : " Duplicate indexes dropped. " ,
} ,
" Some duplicates are still there. " : {
" fr " : " Des doublons subsistent. " ,
" en " : " Some duplicates are still there. " ,
} ,
[ADD] migration: prédire la copie de site qui rendra 500 après le palier
`check_cow_views` ne voyait que la rupture qui ARRÊTE la migration : la
forme que l'arch doit avoir change. Deux autres la laissent finir sans un
mot — un ancrage qu'une vue héritière de la cible réclame, un t-call vers
un gabarit que la cible ne livre plus — et ne se voient qu'à l'ouverture
de la page, c'est-à-dire jamais avant la fin.
Rejouée sur la chaîne 12 → 18, la prédiction nomme le défaut de /contact
dès le palier 13 → 14, en 0,8 s. Il avait attendu six paliers.
Ces copies ne sont PAS neutralisées : chacune porte une page écrite par
quelqu'un. Le pilote appelle la réparation après OpenUpgrade, là où la
vue module porte enfin l'arch de la cible.
--- EN ---
`check_cow_views` only saw the break that STOPS the migration: the shape
the arch must have changes. Two others let it finish without a word — an
anchor a target child view requires, a t-call to a template the target no
longer ships — and only show when the page is opened, which is to say
never before the end.
Replayed on the 12 → 18 chain, the prediction names /contact's defect as
early as the 13 → 14 step, in 0.8 s. It had waited six steps.
Those copies are NOT neutralized: each holds a page someone wrote. The
driver calls the repair after OpenUpgrade, where the module view finally
carries the target's arch.
Assisted-by: Claude Opus 5
2026-08-24 03:48:17 -04:00
" website COW view(s) will survive the bump and then fail " : {
" fr " : " vue(s) COW de site passeront le palier puis échoueront " ,
" en " : " website COW view(s) will survive the bump and then fail " ,
} ,
" to render: " : {
" fr " : " à se rendre : " ,
" en " : " to render: " ,
} ,
" Repair rather than neutralize — the copy holds a page " : {
" fr " : " Réparer plutôt que neutraliser — la copie porte une page " ,
" en " : " Repair rather than neutralize — the copy holds a page " ,
} ,
" someone wrote: " : {
" fr " : " écrite par quelqu ' un : " ,
" en " : " someone wrote: " ,
} ,
[ADD] migration: réparer les copies de site qui ne savent plus se rendre
`check_cow_views` cherche la rupture qui ARRÊTE la migration : la forme
que l'arch doit avoir change. Deux autres la laissent finir sans un mot.
Un ancrage manquant : une vue héritière fait un xpath sur `//t[@t-set=…]`
que le module a gagné en chemin ; la copie, page d'utilisateur, n'est
jamais réécrite. Un t-call pendant : le gabarit appelé n'existe plus dans
la cible. Mesuré sur une 12 → 18 réelle : /contact rendait 500 depuis le
palier 14 → 15, quatre paliers de silence, et le test de fumée le voyait
sans savoir le nommer.
Le premier se répare depuis la vue module, en gardant la page telle que
son auteur l'a écrite. Le second se retire, et l'humain décide.
--- EN ---
`check_cow_views` looks for the break that STOPS the migration: the shape
the arch must have changes. Two others let it finish without a word.
A missing anchor: a child view xpaths on `//t[@t-set=…]` that the module
gained along the way; the copy, a user page, is never rewritten. A
dangling t-call: the template called no longer exists in the target.
Measured on a real 12 → 18: /contact returned 500 since the 14 → 15 step,
four steps of silence, and the smoke test saw it without naming it.
The first is repaired from the module view, keeping the page as its
author wrote it. The second is removed, and the human decides.
Assisted-by: Claude Opus 5
2026-08-24 02:53:24 -04:00
" Every website copy renders again. " : {
" fr " : " Chaque copie de site sait de nouveau se rendre. " ,
" en " : " Every website copy renders again. " ,
} ,
" Some copies still cannot render. " : {
" fr " : " Des copies ne savent toujours pas se rendre. " ,
" en " : " Some copies still cannot render. " ,
} ,
" anchor missing for view " : {
" fr " : " ancrage absent pour la vue " ,
" en " : " anchor missing for view " ,
} ,
" calls a template that is gone: " : {
" fr " : " appelle un gabarit disparu : " ,
" en " : " calls a template that is gone: " ,
} ,
" repair(s) available with --apply " : {
" fr " : " réparation(s) possible(s) avec --apply " ,
" en " : " repair(s) available with --apply " ,
} ,
" cannot be repaired automatically " : {
" fr " : " ne se réparent pas toutes seules " ,
" en " : " cannot be repaired automatically " ,
} ,
" Removing a call removes its block from the page. " : {
" fr " : " Retirer un appel retire son bloc de la page. " ,
" en " : " Removing a call removes its block from the page. " ,
} ,
[ADD] analyse: qui dépend de qui, à l'écran
Le menu Analyse disait quels modules manquent, jamais qui dépend de qui.
« Puis-je retirer celui-ci » se réglait donc à la main : au palier
17 → 18, il a fallu écrire la requête pour savoir si web_responsive
pouvait partir.
L'écran liste les modules et « d » parcourt quatre relations : ce dont il
dépend, ce qui en dépend, tout ce qu'il entraîne, tout ce qui tombe avec
lui. « f » filtre, « / » cherche — une base en porte trois mille. La
réponse qui compte est écrite en toutes lettres : deux dépendants
déclarés dont zéro installé, c'est un retrait sans danger.
--- EN ---
The Analyse menu told which modules were missing, never which depends on
which. « Can I remove this one » was therefore answered by hand: at the
17 → 18 step we had to write the query to learn whether web_responsive
could go.
The screen lists the modules and « d » walks four relations: what it
needs, what needs it, everything it pulls in, everything that falls with
it. « f » filters, « / » searches — a database holds three thousand of
them. The answer that matters is spelled out: two declared dependents of
which zero installed means removal is safe.
Assisted-by: Claude Opus 5
2026-08-23 03:46:08 -04:00
" modules installed " : {
" fr " : " modules installés " ,
" en " : " modules installed " ,
} ,
" of them installed " : {
" fr " : " installé(s) " ,
" en " : " of them installed " ,
} ,
" nothing installed depends on it " : {
" fr " : " rien d ' installé n ' en dépend " ,
" en " : " nothing installed depends on it " ,
} ,
" Reading the modules and their dependencies… " : {
" fr " : " Lecture des modules et de leurs dépendances… " ,
" en " : " Reading the modules and their dependencies… " ,
} ,
" Modules and their dependencies " : {
" fr " : " Modules et leurs dépendances " ,
" en " : " Modules and their dependencies " ,
} ,
" unknown to this database " : {
" fr " : " inconnu de cette base " ,
" en " : " unknown to this database " ,
} ,
" state " : {
" fr " : " état " ,
" en " : " state " ,
} ,
" author " : {
" fr " : " auteur " ,
" en " : " author " ,
} ,
" application " : {
" fr " : " application " ,
" en " : " application " ,
} ,
" yes " : {
" fr " : " oui " ,
" en " : " yes " ,
} ,
" depends on " : {
" fr " : " dépend de " ,
" en " : " depends on " ,
} ,
" needed by " : {
" fr " : " requis par " ,
" en " : " needed by " ,
} ,
" absent " : {
" fr " : " absent " ,
" en " : " absent " ,
} ,
" press d to walk the dependencies " : {
" fr " : " appuyez sur d pour parcourir les dépendances " ,
" en " : " press d to walk the dependencies " ,
} ,
" installed on missing dependencies " : {
" fr " : " installé sur des dépendances absentes " ,
" en " : " installed on missing dependencies " ,
} ,
" broken dependency(ies) " : {
" fr " : " dépendance(s) cassée(s) " ,
" en " : " broken dependency(ies) " ,
} ,
" What it needs, directly " : {
" fr " : " Ce dont il a besoin, directement " ,
" en " : " What it needs, directly " ,
} ,
" What needs it, directly " : {
" fr " : " Ce qui en a besoin, directement " ,
" en " : " What needs it, directly " ,
} ,
" Everything it pulls in " : {
" fr " : " Tout ce qu ' il entraîne " ,
" en " : " Everything it pulls in " ,
} ,
" Everything that falls with it " : {
" fr " : " Tout ce qui tombe avec lui " ,
" en " : " Everything that falls with it " ,
} ,
" all modules " : {
" fr " : " tous les modules " ,
" en " : " all modules " ,
} ,
" installed only " : {
" fr " : " installés seulement " ,
" en " : " installed only " ,
} ,
" not installed " : {
" fr " : " non installés " ,
" en " : " not installed " ,
} ,
" broken dependencies " : {
" fr " : " dépendances cassées " ,
" en " : " broken dependencies " ,
} ,
[ADD] analyse: i, u et t — installés, en cours, applications
Trois filtres sous la main plutôt qu'un cycle à parcourir. « u » est le
seul qui montrait quelque chose d'invisible jusqu'ici : les états de
passage — to install, to upgrade, to remove — qu'Odoo traverse et ne
devrait pas garder. Mesuré sur test_neutralize_upgrade_13 en pleine
migration : 22 modules figés en « to remove ».
L'en-tête les annonce, le rapport texte les nomme avec leur état, et
« non installés » ne les ramasse plus : un module en « to remove » EST
encore installé, il s'en va. La même touche fait l'aller et le retour.
--- EN ---
Three filters under the hand rather than a cycle to walk. « u » is the
only one showing something invisible until now: the transient states —
to install, to upgrade, to remove — that Odoo passes through and should
not keep. Measured on test_neutralize_upgrade_13 mid-migration:
22 modules frozen in « to remove ».
The header announces them, the text report names them with their state,
and « not installed » no longer collects them: a module in « to remove »
IS still installed, it is on its way out. The same key goes and returns.
Assisted-by: Claude Opus 5
2026-08-23 04:03:18 -04:00
" in progress " : {
" fr " : " en cours " ,
" en " : " in progress " ,
} ,
" apps only " : {
" fr " : " applications seulement " ,
" en " : " apps only " ,
} ,
" left in a transient state " : {
" fr " : " laissés dans un état de passage " ,
" en " : " left in a transient state " ,
} ,
" Installed " : {
" fr " : " Installés " ,
" en " : " Installed " ,
} ,
" In progress " : {
" fr " : " En cours " ,
" en " : " In progress " ,
} ,
" Apps " : {
" fr " : " Applications " ,
" en " : " Apps " ,
} ,
[ADD] analyse: qui dépend de qui, à l'écran
Le menu Analyse disait quels modules manquent, jamais qui dépend de qui.
« Puis-je retirer celui-ci » se réglait donc à la main : au palier
17 → 18, il a fallu écrire la requête pour savoir si web_responsive
pouvait partir.
L'écran liste les modules et « d » parcourt quatre relations : ce dont il
dépend, ce qui en dépend, tout ce qu'il entraîne, tout ce qui tombe avec
lui. « f » filtre, « / » cherche — une base en porte trois mille. La
réponse qui compte est écrite en toutes lettres : deux dépendants
déclarés dont zéro installé, c'est un retrait sans danger.
--- EN ---
The Analyse menu told which modules were missing, never which depends on
which. « Can I remove this one » was therefore answered by hand: at the
17 → 18 step we had to write the query to learn whether web_responsive
could go.
The screen lists the modules and « d » walks four relations: what it
needs, what needs it, everything it pulls in, everything that falls with
it. « f » filters, « / » searches — a database holds three thousand of
them. The answer that matters is spelled out: two declared dependents of
which zero installed means removal is safe.
Assisted-by: Claude Opus 5
2026-08-23 03:46:08 -04:00
" Dependencies " : {
" fr " : " Dépendances " ,
" en " : " Dependencies " ,
} ,
" Filter " : {
" fr " : " Filtre " ,
" en " : " Filter " ,
} ,
" Search " : {
" fr " : " Chercher " ,
" en " : " Search " ,
} ,
" module " : {
" fr " : " module " ,
" en " : " module " ,
} ,
" module name… " : {
" fr " : " nom de module… " ,
" en " : " module name… " ,
} ,
2026-08-21 05:00:38 -04:00
" Show every entry " : {
2026-08-21 23:07:54 -04:00
" fr " : " 📜 Tout afficher " ,
" en " : " 📜 Show every entry " ,
2026-08-21 05:00:38 -04:00
} ,
" List the known packages " : {
2026-08-21 23:07:54 -04:00
" fr " : " 📚 Lister les packages connus " ,
" en " : " 📚 List the known packages " ,
2026-08-21 05:00:38 -04:00
} ,
" Already repaired: the group exists. " : {
" fr " : " Déjà réparé : le groupe existe. " ,
" en " : " Already repaired: the group exists. " ,
} ,
" DMS access groups: " : {
" fr " : " Groupes d ' accès DMS : " ,
" en " : " DMS access groups: " ,
} ,
" DMS documents in the database " : {
" fr " : " Documents DMS dans la base " ,
" en " : " DMS documents in the database " ,
} ,
" No DMS user to test visibility with. " : {
" fr " : " Aucun utilisateur DMS pour éprouver la visibilité. " ,
" en " : " No DMS user to test visibility with. " ,
} ,
" Nothing written. Re-run with --apply. " : {
" fr " : " Rien d ' écrit. Relancer avec --apply. " ,
" en " : " Nothing written. Re-run with --apply. " ,
} ,
" Now visible to " : {
" fr " : " Désormais visibles pour " ,
" en " : " Now visible to " ,
} ,
" Visible to " : {
" fr " : " Visibles pour " ,
" en " : " Visible to " ,
} ,
" Would create one access group over " : {
" fr " : " Créerait un groupe d ' accès sur " ,
" en " : " Would create one access group over " ,
} ,
" file(s) " : {
" fr " : " fichier(s) " ,
" en " : " file(s) " ,
} ,
" folder(s) " : {
" fr " : " dossier(s) " ,
" en " : " folder(s) " ,
} ,
" root folder(s) " : {
" fr " : " dossier(s) racine " ,
" en " : " root folder(s) " ,
} ,
2026-08-21 05:25:23 -04:00
" No DMS in this database, nothing to repair. " : {
" fr " : " Pas de DMS dans cette base, rien à réparer. " ,
" en " : " No DMS in this database, nothing to repair. " ,
} ,
" Every one of them is visible to someone. " : {
" fr " : " Chacun est visible par quelqu ' un. " ,
" en " : " Every one of them is visible to someone. " ,
} ,
" No internal user to test visibility with. " : {
" fr " : " Aucun utilisateur interne pour éprouver la visibilité. " ,
" en " : " No internal user to test visibility with. " ,
} ,
" The data is there; a global rule hides all of it. " : {
" fr " : " Les données sont là ; une règle globale les masque entièrement. " ,
" en " : " The data is there; a global rule hides all of it. " ,
} ,
" checked against " : {
" fr " : " éprouvés avec " ,
" en " : " checked against " ,
} ,
" internal user(s) " : {
" fr " : " utilisateur(s) interne(s) " ,
" en " : " internal user(s) " ,
} ,
" model(s) nobody can see a single row of " : {
" fr " : " modèle(s) dont personne ne voit une seule ligne " ,
" en " : " model(s) nobody can see a single row of " ,
} ,
" model(s) with a global rule and some data, " : {
" fr " : " modèle(s) avec une règle globale et des données, " ,
" en " : " model(s) with a global rule and some data, " ,
} ,
" row(s) " : {
" fr " : " ligne(s) " ,
" en " : " row(s) " ,
} ,
[ADD] migration quality: lay OpenUpgrade's declared changes over the real ones
The report counted + and -. A field that stops being stored is neither:
it loses its column and keeps its existence. Read as a loss it cries
wolf every step; ignored it would mask a real one. OpenUpgrade already
knows, in the upgrade_analysis.txt files the checkout carries -- the
same source the OCA coverage pages are generated from, read locally, at
the checkout's exact version.
Three categories keep the signal from drowning: a field whose model went
away is not a second finding (544 of them on one real step), a field of
a module OpenUpgrade does not analyse was never its business, and the
rest is grouped by field name -- __last_update counted itself 391 times.
--- FR ---
Le rapport comptait des + et des -. Un champ qui cesse d'être stocké
n'est ni l'un ni l'autre : il perd sa colonne et garde son existence. Lu
comme une perte, il alarme à chaque palier ; ignoré, il en masquerait
une vraie. OpenUpgrade le sait déjà, dans les upgrade_analysis.txt que
porte le checkout -- la source des pages de couverture de l'OCA, lue
localement, à la version exacte.
Trois catégories évitent que le signal se noie : un champ dont le modèle
a disparu n'est pas une trouvaille de plus (544 sur un palier réel), un
champ d'un module non analysé n'a jamais relevé d'OpenUpgrade, et le
reste est groupé par nom -- __last_update se comptait 391 fois.
Assisted-by: Claude Opus 5
2026-08-21 05:55:54 -04:00
" Against OpenUpgrade " : {
" fr " : " Face à OpenUpgrade " ,
" en " : " Against OpenUpgrade " ,
} ,
" NOT declared by OpenUpgrade " : {
" fr " : " NON déclarés par OpenUpgrade " ,
" en " : " NOT declared by OpenUpgrade " ,
} ,
" No OpenUpgrade analysis for this step. " : {
" fr " : " Pas d ' analyse OpenUpgrade pour ce palier. " ,
" en " : " No OpenUpgrade analysis for this step. " ,
} ,
" became a per-company jsonb column " : {
" fr " : " passés en colonne jsonb par société " ,
" en " : " became a per-company jsonb column " ,
} ,
" computed now — the field remains, the column does not " : {
" fr " : " devenus calculés — le champ demeure, la colonne non " ,
" en " : " computed now — the field remains, the column does not " ,
} ,
" core module(s) analysed " : {
" fr " : " module(s) cœur analysé(s) " ,
" en " : " core module(s) analysed " ,
} ,
" declared obsolete " : {
" fr " : " déclarés obsolètes " ,
" en " : " declared obsolete " ,
} ,
" declared removed " : {
" fr " : " déclarés supprimés " ,
" en " : " declared removed " ,
} ,
" field(s) lost " : {
" fr " : " champ(s) perdu(s) " ,
" en " : " field(s) lost " ,
} ,
" model(s) lost " : {
" fr " : " modèle(s) perdu(s) " ,
" en " : " model(s) lost " ,
} ,
" moved to another module " : {
" fr " : " déplacés vers un autre module " ,
" en " : " moved to another module " ,
} ,
" renamed by Odoo " : {
" fr " : " renommés par Odoo " ,
" en " : " renamed by Odoo " ,
} ,
" their model went away too " : {
" fr " : " leur modèle a disparu aussi " ,
" en " : " their model went away too " ,
} ,
" in a module OpenUpgrade does not analyse " : {
" fr " : " dans un module qu ' OpenUpgrade n ' analyse pas " ,
" en " : " in a module OpenUpgrade does not analyse " ,
} ,
" model(s) " : {
" fr " : " modèle(s) " ,
" en " : " model(s) " ,
} ,
" other field name(s) " : {
" fr " : " autre(s) nom(s) de champ " ,
" en " : " other field name(s) " ,
} ,
[ADD] migration: repair views whose stored type contradicts their parent
Odoo refused to load a database: it validated a <search> arch with tree
rules. The view is not a COW copy -- the COW tools were right to say
they had nothing to reset -- its stored `type` simply lies.
Nothing would ever have fixed it. In ir_ui_view.py, `type` is filled in
`create`, and only when absent; `write` never recomputes it. A wrong
value stays wrong, and `-u module` fails on the validation it causes
before it could rewrite anything.
Plain SQL, no ORM: the registry is what will not load, and a repair that
needed Odoo to fix what stops Odoo would be useless.
The rule is Odoo's own -- an inherited view takes its parent's type --
and it holds: zero disagreement on a pristine 18 install and on four
migrated databases, exactly one on the database that refused to load.
--- FR ---
Odoo refusait de charger une base : il validait un arch <search> avec
les règles d'un tree. La vue n'est pas une copie COW — les outils COW
avaient raison de dire qu'ils n'avaient rien à réinitialiser — c'est son
`type` stocké qui ment.
Rien ne l'aurait jamais réparé. Dans ir_ui_view.py, `type` est rempli
dans `create`, et seulement s'il est absent ; `write` ne le recalcule
jamais. Une valeur fausse le reste, et `-u module` échoue sur la
validation qu'elle provoque avant de pouvoir réécrire quoi que ce soit.
En SQL, sans ORM : c'est le registre qui ne charge plus, et une
réparation qui aurait besoin d'Odoo ne servirait à rien.
La règle est celle d'Odoo — une vue héritée prend le type de son parent
— et elle tient : zéro écart sur une 18 neuve et sur quatre bases
migrées, exactement un sur celle qui refusait de charger.
Assisted-by: Claude Opus 5
2026-08-22 00:46:42 -04:00
" Filestore of " : {
" fr " : " Filestore de " ,
" en " : " Filestore of " ,
} ,
" file(s) missing " : {
" fr " : " fichier(s) absent(s) " ,
" en " : " file(s) missing " ,
} ,
" file(s) on disk " : {
" fr " : " fichier(s) sur disque " ,
" en " : " file(s) on disk " ,
} ,
" file(s) sit in nested filestores Odoo never reads. " : {
" fr " : " fichier(s) échoué(s) dans des filestores imbriqués qu ' Odoo ne lit jamais. " ,
" en " : " file(s) sit in nested filestores Odoo never reads. " ,
} ,
" intact in another database ' s filestore " : {
" fr " : " intacts dans le filestore d ' une autre base " ,
" en " : " intact in another database ' s filestore " ,
} ,
" its field no longer exists — nothing reads it " : {
" fr " : " leur champ n ' existe plus — rien ne les lit " ,
" en " : " its field no longer exists — nothing reads it " ,
} ,
" nowhere to be found — truly lost " : {
" fr " : " introuvables — réellement perdus " ,
" en " : " nowhere to be found — truly lost " ,
} ,
" still in a backup zip " : {
" fr " : " encore dans une sauvegarde zip " ,
" en " : " still in a backup zip " ,
} ,
" stored attachment(s) " : {
" fr " : " pièce(s) jointe(s) stockée(s) " ,
" en " : " stored attachment(s) " ,
} ,
" stranded in a nested filestore Odoo never reads " : {
" fr " : " échoués dans un filestore imbriqué qu ' Odoo ne lit pas " ,
" en " : " stranded in a nested filestore Odoo never reads " ,
} ,
" Files " : {
" fr " : " Fichiers " ,
" en " : " Files " ,
} ,
" Attachment files missing from the filestore " : {
" fr " : " 🗄 Fichiers de pièces jointes absents du filestore " ,
" en " : " 🗄 Attachment files missing from the filestore " ,
} ,
" Scanning filestores and backups… " : {
" fr " : " Examen des filestores et des sauvegardes… " ,
" en " : " Scanning filestores and backups… " ,
} ,
" Filestore restored: " : {
" fr " : " Filestore restauré : " ,
" en " : " Filestore restored: " ,
} ,
" file(s) in place " : {
" fr " : " fichier(s) en place " ,
" en " : " file(s) in place " ,
} ,
" Filestore restore looks wrong for " : {
" fr " : " Restauration du filestore suspecte pour " ,
" en " : " Filestore restore looks wrong for " ,
} ,
" landed in a nested filestore Odoo never reads " : {
" fr " : " atterris dans un filestore imbriqué qu ' Odoo ne lit jamais " ,
" en " : " landed in a nested filestore Odoo never reads " ,
} ,
" To fix: " : {
" fr " : " Pour corriger : " ,
" en " : " To fix: " ,
} ,
" never landed at all " : {
" fr " : " ne sont arrivés nulle part " ,
" en " : " never landed at all " ,
} ,
" record still exists " : {
" fr " : " l ' enregistrement existe encore " ,
" en " : " record still exists " ,
} ,
" record is gone — nothing will miss it " : {
" fr " : " l ' enregistrement a disparu — personne ne la cherchera " ,
" en " : " record is gone — nothing will miss it " ,
} ,
" Delete these " : {
" fr " : " Supprimer ces " ,
" en " : " Delete these " ,
} ,
" Directory: " : {
" fr " : " Dossier : " ,
" en " : " Directory: " ,
} ,
" No nested filestore to tidy. " : {
" fr " : " Pas de filestore imbriqué à ranger. " ,
" en " : " No nested filestore to tidy. " ,
} ,
" Nothing to purge. " : {
" fr " : " Rien à purger. " ,
" en " : " Nothing to purge. " ,
} ,
" Nothing was deleted. " : {
" fr " : " Rien n ' a été supprimé. " ,
" en " : " Nothing was deleted. " ,
} ,
" Nothing was moved. " : {
" fr " : " Rien n ' a été déplacé. " ,
" en " : " Nothing was moved. " ,
} ,
" The purge failed. " : {
" fr " : " La purge a échoué. " ,
" en " : " The purge failed. " ,
} ,
" attachment row(s) deleted. " : {
" fr " : " ligne(s) de pièce jointe supprimée(s). " ,
" en " : " attachment row(s) deleted. " ,
} ,
" attachment row(s) for good? " : {
" fr " : " ligne(s) de pièce jointe, définitivement ? " ,
" en " : " attachment row(s) for good? " ,
} ,
" duplicate(s) removed " : {
" fr " : " doublon(s) supprimé(s) " ,
" en " : " duplicate(s) removed " ,
} ,
" file(s) to move up " : {
" fr " : " fichier(s) à remonter " ,
" en " : " file(s) to move up " ,
} ,
" moved up " : {
" fr " : " remonté(s) " ,
" en " : " moved up " ,
} ,
" pure duplicate(s) to delete " : {
" fr " : " doublon(s) pur(s) à supprimer " ,
" en " : " pure duplicate(s) to delete " ,
} ,
" 🧹 Purge attachments whose field no longer exists " : {
" fr " : " 🧹 Purger les pièces jointes dont le champ n ' existe plus " ,
" en " : " 🧹 Purge attachments whose field no longer exists " ,
} ,
" 🧹 Tidy the nested filestore Odoo never reads " : {
" fr " : " 🧹 Ranger le filestore imbriqué qu ' Odoo ne lit pas " ,
" en " : " 🧹 Tidy the nested filestore Odoo never reads " ,
} ,
" The purge ran but said nothing. " : {
" fr " : " La purge a tourné sans rien annoncer. " ,
" en " : " The purge ran but said nothing. " ,
} ,
" row(s) whose field is gone still hold their file " : {
" fr " : " ligne(s) à champ disparu retiennent encore leur fichier " ,
" en " : " row(s) whose field is gone still hold their file " ,
} ,
" file(s) sit in a nested filestore Odoo never reads. " : {
" fr " : " fichier(s) échoué(s) dans un filestore imbriqué qu ' Odoo ne lit jamais. " ,
" en " : " file(s) sit in a nested filestore Odoo never reads. " ,
} ,
" such file(s) sit in OTHER databases filestores. " : {
" fr " : " fichier(s) de ce genre dorment dans les filestores d ' AUTRES bases. " ,
" en " : " such file(s) sit in OTHER databases filestores. " ,
} ,
" Could not inspect the attachments: " : {
" fr " : " Inspection des pièces jointes impossible : " ,
" en " : " Could not inspect the attachments: " ,
} ,
" Delete them? " : {
" fr " : " Les supprimer ? " ,
" en " : " Delete them? " ,
} ,
" No attachment points at a field that is gone. " : {
" fr " : " Aucune pièce jointe ne pointe vers un champ disparu. " ,
" en " : " No attachment points at a field that is gone. " ,
} ,
" attachment(s) point at a field that no longer exists " : {
" fr " : " pièce(s) jointe(s) pointent vers un champ qui n ' existe plus " ,
" en " : " attachment(s) point at a field that no longer exists " ,
} ,
" Corrected. " : {
" fr " : " Corrigé. " ,
" en " : " Corrected. " ,
} ,
" No module update will fix this: the type is set once, " : {
" fr " : " Aucune mise à jour de module ne corrigera cela : le type est posé une fois, " ,
" en " : " No module update will fix this: the type is set once, " ,
} ,
" The correction failed. " : {
" fr " : " La correction a échoué. " ,
" en " : " The correction failed. " ,
} ,
" at creation. Re-run with --apply. " : {
" fr " : " à la création. Relancer avec --apply. " ,
" en " : " at creation. Re-run with --apply. " ,
} ,
" their inheritance " : {
" fr " : " leur héritage " ,
" en " : " their inheritance " ,
} ,
" view(s) whose stored type contradicts " : {
" fr " : " vue(s) dont le type stocké contredit " ,
" en " : " view(s) whose stored type contradicts " ,
} ,
" Fix views whose type contradicts their " : {
" fr " : " Corriger les vues dont le type contredit leur " ,
" en " : " Fix views whose type contradicts their " ,
} ,
" inheritance " : {
" fr " : " héritage " ,
" en " : " inheritance " ,
} ,
" Correct them? " : {
" fr " : " Les corriger ? " ,
" en " : " Correct them? " ,
} ,
[ADD] migration: offer the theme uninstall where the theme is blamed
The question was asked once, at step one, and a flag kept it from ever
returning. But a theme only becomes incompatible at a given bump, hours
later -- asking at the start could not cover it. Entry [6] appears when
TWO conditions hold: a theme is installed, and the recent output names
it. One condition alone would offer to wreck the site's design over an
error that has nothing to do with it.
Also, the 17 to 18 rename OpenUpgrade declares and never applies:
forum.post / tag_ids : column1 is now 'forum_post_id' ('forum_id')
website_forum/18.0.1.2/ holds only an analysis, no script, so the load
dies on a foreign key to a column that does not exist and leaves the
database half migrated.
--- FR ---
La question était posée une fois, à l'étape 1, et un drapeau
l'empêchait de revenir. Or un thème ne devient incompatible qu'à un
palier donné, des heures plus tard : la poser au départ ne pouvait pas
suffire. L'entrée [6] paraît quand DEUX conditions tiennent — un thème
installé, et la sortie récente qui le nomme. Une seule offrirait de
casser le design du site pour une panne étrangère.
Et le renommage 17 → 18 qu'OpenUpgrade déclare sans jamais l'appliquer :
forum.post / tag_ids : column1 is now 'forum_post_id' ('forum_id')
website_forum/18.0.1.2/ ne porte qu'une analyse, aucun script : le
chargement meurt sur une clé étrangère vers une colonne absente et
laisse la base à moitié migrée.
Assisted-by: Claude Opus 5
2026-08-22 01:52:53 -04:00
" Uninstall the theme(s) the error names " : {
" fr " : " Désinstaller le(s) thème(s) que l ' erreur nomme " ,
" en " : " Uninstall the theme(s) the error names " ,
} ,
[ADD] migration: convert the <tree> tags Odoo 18 renamed to <list>
OpenUpgrade converts the COLUMN and never the ARCH:
def _fix_list_view_type(cr):
cr.execute("UPDATE ir_ui_view SET type='list' WHERE type='tree'")
so the load dies on "the root node of a list view should be <list>".
A module view recovers at its next update, which rewrites the arch from
XML. A view with no xmlid -- a hand-made list, a website copy -- is
rewritten by nothing and stays broken forever. The report says which is
which, because the two call for different work.
Every occurrence, not just the root: a <tree> nested in a form is a
one2many list and is refused just the same. Odoo 18's own addons hold
none, so nothing legitimate is at risk. Gated on version 18: before it,
the tag is correct and "fixing" it would break healthy views.
Four tests run the SQL against a real PostgreSQL. A text assertion
cannot see what a query does -- it took a live run to prove every
language of the jsonb arch is converted, not just en_US.
--- FR ---
OpenUpgrade convertit la COLONNE et jamais l'ARCH, d'où l'échec sur « le
nœud racine d'une vue list devrait être <list> ». Une vue de module s'en
remet à sa prochaine mise à jour ; une vue sans xmlid — liste faite
main, copie de site — n'est réécrite par rien. Le rapport distingue les
deux, car le travail diffère.
Toutes les occurrences, pas seulement la racine : un <tree> imbriqué
dans un formulaire est une liste one2many, refusée pareillement. Les
addons d'Odoo 18 n'en portent aucun. Bridé à la 18 : avant, la balise
est juste.
Quatre tests exécutent le SQL contre un vrai PostgreSQL. Une assertion
sur du texte ne voit pas ce qu'une requête fait — il a fallu l'exécuter
pour prouver que toutes les langues du jsonb sont converties.
Assisted-by: Claude Opus 5
2026-08-22 02:34:22 -04:00
" Every view agrees with what Odoo expects. " : {
" fr " : " Toutes les vues s ' accordent avec ce qu ' Odoo attend. " ,
" en " : " Every view agrees with what Odoo expects. " ,
} ,
" custom — nothing will ever rewrite it " : {
" fr " : " faite main — rien ne la réécrira jamais " ,
" en " : " custom — nothing will ever rewrite it " ,
} ,
" from a module — a module update also fixes it " : {
" fr " : " d ' un module — une mise à jour la répare aussi " ,
" en " : " from a module — a module update also fixes it " ,
} ,
" renamed to <list> " : {
" fr " : " a renommée en <list> " ,
" en " : " renamed to <list> " ,
} ,
" view(s) still carry a <tree> tag Odoo 18 " : {
" fr " : " vue(s) portent encore une balise <tree> qu ' Odoo 18 " ,
" en " : " view(s) still carry a <tree> tag Odoo 18 " ,
} ,
[FIX] smoke: name the URL that actually failed, keep the previous log
The report named the sitemap URL, never the one the redirect chain
ended on. On this site every page goes through two or three hops --
measured, 146 for 55 pages, between the language prefix and the
canonical slug -- so a 500 at the end was reported against a page that
answers perfectly well. One goes and checks it, finds it healthy, and
concludes the tool is wrong.
urllib carries the answer: HTTPError.url is the URL that produced the
error, not the one requested. fetch now returns it and the report shows
it when it differs.
A timeout was never reported as 500 -- fetch returns 0 and the report
writes "no answer" -- but the evidence for a real 500 did evaporate: the
Odoo log was opened with "w", so replaying the test erased the trace of
the failure one had just seen. One generation is kept now.
--- FR ---
Le rapport nommait l'URL du sitemap, jamais celle où la chaîne de
redirections aboutit. Sur ce site chaque page en traverse deux ou trois
— mesuré, 146 pour 55 pages — donc un 500 au bout était imputé à une
page qui répond très bien. On va la vérifier, on la trouve saine, et
l'on conclut que l'outil se trompe.
urllib porte la réponse : HTTPError.url est l'URL qui a produit
l'erreur. `fetch` la rend, et le rapport l'affiche quand elle diffère.
Un dépassement de délai n'a jamais été rendu comme un 500 — `fetch`
rend 0, écrit « aucune réponse » — mais la preuve d'un vrai 500
s'évaporait : le journal Odoo était ouvert en « w », donc rejouer le
test effaçait la trace qu'on venait de voir. Une génération est gardée.
Assisted-by: Claude Opus 5
2026-08-22 03:19:45 -04:00
" failed at " : {
" fr " : " a échoué sur " ,
" en " : " failed at " ,
} ,
2026-08-21 05:00:38 -04:00
" Census " : {
" fr " : " Recensement " ,
" en " : " Census " ,
} ,
" Compared against nothing — census only. " : {
" fr " : " Comparé à rien — recensement seul. " ,
" en " : " Compared against nothing — census only. " ,
} ,
" Default package: " : {
" fr " : " Package par défaut : " ,
" en " : " Default package: " ,
} ,
" Every suggested module is installed. " : {
" fr " : " Tous les modules suggérés sont installés. " ,
" en " : " Every suggested module is installed. " ,
} ,
" Installed modules by author " : {
" fr " : " Modules installés, par auteur " ,
" en " : " Installed modules by author " ,
} ,
" Modules of " : {
" fr " : " Modules de " ,
" en " : " Modules of " ,
} ,
" No default package known for this version " : {
" fr " : " Aucun package par défaut connu pour cette version " ,
" en " : " No default package known for this version " ,
} ,
" Odoo " : {
" fr " : " Odoo " ,
" en " : " Odoo " ,
} ,
" absent from the addons path — sync the repo first " : {
" fr " : " hors du chemin des addons — synchroniser le dépôt d ' abord " ,
" en " : " absent from the addons path — sync the repo first " ,
} ,
" also needs " : {
" fr " : " réclame aussi " ,
" en " : " also needs " ,
} ,
" half-way — finish the pending update " : {
" fr " : " à mi-chemin — finir la mise à jour en attente " ,
" en " : " half-way — finish the pending update " ,
} ,
" installed " : {
" fr " : " déjà en place " ,
" en " : " installed " ,
} ,
" known " : {
" fr " : " connus " ,
" en " : " known " ,
} ,
" known to the database — install it " : {
" fr " : " dans la base — à installer " ,
" en " : " known to the database — install it " ,
} ,
" present but broken — fix the dependency " : {
" fr " : " en panne de dépendance — à réparer " ,
" en " : " present but broken — fix the dependency " ,
} ,
" suggested module(s) " : {
" fr " : " module(s) suggéré(s) " ,
" en " : " suggested module(s) " ,
} ,
" suggested module(s) not installed " : {
" fr " : " module(s) suggéré(s) non installé(s) " ,
" en " : " suggested module(s) not installed " ,
} ,
" unknown author " : {
" fr " : " auteur inconnu " ,
" en " : " unknown author " ,
} ,
[ADD] migration quality: explain ir_property and tracking values
ir_property never empties, it GROWS — 211 rows in 12, 457 in 17 — then
vanishes in 18. Its fields are jsonb columns there, verified on
res_partner.property_payment_term_id.
mail_tracking_value is a REAL loss, not a rename: `field` goes from
varchar to a foreign key in 14, and what would not resolve was deleted.
1528 of the 2336 belonged to the removed agile module; 706 more survived
under a new name. New kind "pruned" so the report cannot call a living
table retired.
--- FR ---
ir_property ne se vide jamais, elle GROSSIT — 211 lignes en 12, 457 en
17 — puis disparaît en 18. Ses champs y sont des colonnes jsonb, vérifié
sur res_partner.property_payment_term_id.
mail_tracking_value est une VRAIE perte, pas un renommage : `field`
passe de varchar à clé étrangère en 14, et ce qui ne se résolvait pas a
été supprimé. 1528 des 2336 venaient du module agile retiré ; 706 autres
ont survécu sous un autre nom. Nouveau genre « pruned » pour que le
rapport ne déclare pas retirée une table bien vivante.
Assisted-by: Claude Opus 5
2026-08-21 03:31:28 -04:00
" rows dropped, the table remains " : {
" fr " : " lignes supprimées, la table demeure " ,
" en " : " rows dropped, the table remains " ,
[ADD] migration quality: tell Odoo's own redesigns from real losses
« 81 tables lost rows » buried the questions worth asking. The largest of
them — ir_translation, 32 984 rows — is a redesign Odoo made in 16, when
translations moved into jsonb columns. Putting redesigns and real losses
on the same footing is the surest way not to see the second kind.
A map now names what Odoo moves or retires, and the report splits the
list in two: unexplained first, then what Odoo did on purpose. On a real
12 → 18 the split is 57 and 14, and the partition is exact — an explained
loss is annotated, never removed, because removing it was the one thing
that could hide a real one.
Every entry was VERIFIED on that migration by counting both sides, not
written from memory: crm_lead_tag 6 → crm_tag +6, mail_channel 26 →
discuss_channel +26, account_invoice 651 → account_move +441. A map
written from memory would explain losses that are not losses.
And a merge whose target gained nothing says so: the explanation does not
hold, and that is exactly what the map was meant to surface.
--- FR ---
[ADD] qualité de migration : distinguer les refontes d'Odoo des vraies pertes
« 81 tables ont perdu des lignes » enterrait les questions qui valaient la
peine. La plus grosse — ir_translation, 32 984 lignes — est une refonte
d'Odoo 16, quand les traductions sont passées en colonnes jsonb.
Une carte nomme désormais ce qu'Odoo déplace ou retire, et le rapport
sépare la liste en deux : l'inexpliqué d'abord, puis ce qu'Odoo a fait
exprès. Sur une vraie 12 → 18 : 57 et 14, et la partition est exacte —
une perte expliquée est annotée, jamais retirée.
Chaque entrée a été VÉRIFIÉE sur cette migration en comptant les deux
côtés : crm_lead_tag 6 → crm_tag +6, mail_channel 26 → discuss_channel
+26, account_invoice 651 → account_move +441.
Et une fusion dont la table d'accueil n'a rien reçu le dit : l'explication
ne tient pas, et c'est précisément ce que la carte devait révéler.
Assisted-by: Claude Opus 5
2026-08-20 02:42:18 -04:00
} ,
" retired from the database " : {
" fr " : " retirée de la base " ,
" en " : " retired from the database " ,
} ,
" but it gained nothing " : {
" fr " : " mais elle n ' a rien reçu " ,
" en " : " but it gained nothing " ,
} ,
" there " : {
" fr " : " là-bas " ,
" en " : " there " ,
} ,
[ADD] migration quality: biggest losses first, and the full lists behind « d »
Fifty-seven losses are read from the top, not in alphabetical order. They
are now sorted by rows LOST, not by rows there before: a table of ten
thousand that drops two matters less than one of a thousand that empties.
« d » cycles the pane through the complete lists — modules, models,
fields, COW copies, tables — and back to the summary. The summary cuts at
eight entries and is right to; but when one is looking for whether ONE
module survived, the truncated list does not answer, and that is exactly
when it is needed.
Two inventories were missing and both matter. FIELDS: a lost field is a
lost column of data, finer than a model, which can survive emptied of
half of its own. COW COPIES by key: it is the key one resets, and by the
key one finds them again across versions. On a real 12 → 18 they read
11 036 → 15 069 fields and 64 → 131 copies, of which 28 disappeared —
customisations nothing was reporting until now.
One mode replaces two flags: « show missing files » and « show the model
list » cannot both be true, and two booleans let that impossible state be
written. The mode is named in the sub-title, because a pane that changes
without saying why reads as a broken screen.
--- FR ---
[ADD] qualité de migration : les plus grosses pertes d'abord, les listes derrière « d »
Cinquante-sept pertes se lisent par le haut. Elles sont triées sur le
volume PERDU, non sur le volume présent avant : une table de dix mille qui
en perd deux compte moins qu'une de mille qui se vide.
« d » fait défiler le panneau à travers les listes entières — modules,
modèles, champs, copies COW, tables — puis revient au résumé.
Deux inventaires manquaient. Les CHAMPS : un champ perdu est une colonne
de données perdue. Les COPIES COW par leur clé : sur une vraie 12 → 18,
28 ont disparu — des personnalisations que rien ne signalait.
Un seul mode remplace deux drapeaux, qui laissaient écrire un état
impossible. Il est nommé dans le sous-titre.
Assisted-by: Claude Opus 5
2026-08-20 03:47:02 -04:00
" lost " : {
" fr " : " perdus " ,
" en " : " lost " ,
} ,
" gained " : {
" fr " : " gagnés " ,
" en " : " gained " ,
} ,
" fields " : {
" fr " : " champs " ,
" en " : " fields " ,
} ,
" cow " : {
" fr " : " copies COW " ,
" en " : " cow " ,
} ,
" tables " : {
" fr " : " tables " ,
" en " : " tables " ,
} ,
" Details " : {
" fr " : " Détails " ,
" en " : " Details " ,
} ,
" summary " : {
" fr " : " résumé " ,
" en " : " summary " ,
} ,
[ADD] migration: run the OCA database cleanup before testing the pages
The eight purges are not independent: purging a model frees the columns
that referenced it, purging a table frees the data pointing at it. One
pass is never enough, so the requested order runs again until a full
pass repairs nothing new.
Refusals are expected — a foreign key still holds, a module says no.
Purging a list at once loses everything to the first one, so each entry
purges inside its own savepoint: a refusal rolls back that entry alone.
What one pass could not take, the next may, once its neighbours are
gone. Leftovers are reported as a warning: a database can carry some
that nothing removes, and stopping there would help no one.
It refuses a version mismatch. Measured while building it: a shell on
Odoo 14 opened against a 17.0 database went rewriting ir_model before
dying on a jsonb it did not know. An older Odoo does not merely fail on
a newer database — it writes on the way.
--- FR ---
[ADD] migration : lancer le nettoyage OCA avant de tester les pages
Les huit purges ne sont pas indépendantes : purger un modèle libère les
colonnes qui le référençaient, purger une table libère les données qui
la visaient. Une passe ne suffit jamais, l'ordre demandé est donc rejoué
jusqu'à ce qu'une passe entière ne répare plus rien.
Les refus sont attendus — une clé étrangère tient, un module dit non.
Purger d'un bloc perdrait tout au premier : chaque entrée passe dans son
propre point de reprise, un refus n'emporte que la sienne. Ce qu'une
passe n'a pu prendre, la suivante le peut, une fois les voisines
parties. Les restes sont un avertissement : une base peut en porter que
rien ne retire, et s'arrêter là n'aiderait personne.
Il refuse une version qui ne correspond pas. Mesuré en le construisant :
un shell en Odoo 14 ouvert sur une base 17.0 est parti réécrire ir_model
avant de mourir sur un jsonb inconnu de lui. Un Odoo plus ancien
n'échoue pas simplement sur une base plus récente — il écrit en chemin.
Assisted-by: Claude Opus 5
2026-08-17 09:05:03 -04:00
" Clean the database before testing the pages? " : {
" fr " : " Nettoyer la base avant de tester les pages ? " ,
" en " : " Clean the database before testing the pages? " ,
} ,
[ADD] migration: an auto-run that takes the default after five seconds
A migration asks dozens of questions whose answer is almost always the
one offered, and someone has to stand there pressing Enter for hours.
Asked before the version choice — so it covers that choice too, now
that it has a default — and off by default: it decides in your place.
Every prompt of the migration goes through one reader, seventeen of
them. A prompt left as a bare input() would know nothing of auto mode
and would block the run with nothing to say why; a test reads the
function and rejects any that appears.
click.prompt had to go: it cannot hand back control after a delay, so
auto would have stopped at the first question. select() rather than a
thread or an alarm — a thread would leave a blocked input() behind it,
stealing the next keystroke.
--- FR ---
[ADD] migration : une auto-exécution qui prend le défaut après cinq secondes
Une migration pose des dizaines de questions dont la réponse est presque
toujours celle proposée, et il faut rester devant à taper Entrée pendant
des heures. Posée avant le choix de version — donc elle vaut aussi pour
lui, maintenant qu'il a un défaut — et éteinte par défaut : elle décide
à votre place.
Toutes les invites de la migration passent par une seule lecture,
dix-sept d'un coup. Une invite laissée en input() nu ne saurait rien du
mode auto et bloquerait la migration sans rien dire ; un test lit la
fonction et refuse toute apparition.
click.prompt devait partir : il ne sait pas rendre la main après un
délai, l'auto se serait arrêté à la première question. select() plutôt
qu'un fil ou une alarme — un fil laisserait un input() bloqué derrière
lui, qui volerait la frappe suivante.
Assisted-by: Claude Opus 5
2026-08-17 17:35:31 -04:00
" Auto-run: take the default answer after " : {
" fr " : " Auto-exécution : prendre la réponse par défaut après " ,
" en " : " Auto-run: take the default answer after " ,
} ,
" seconds? " : {
" fr " : " secondes ? " ,
" en " : " seconds? " ,
} ,
" Auto-run on: each prompt waits " : {
" fr " : " Auto-exécution activée : chaque invite attend " ,
" en " : " Auto-run on: each prompt waits " ,
} ,
" seconds, then takes its default. " : {
" fr " : " secondes, puis prend son défaut. " ,
" en " : " seconds, then takes its default. " ,
} ,
" (default) " : {
" fr " : " (défaut) " ,
" en " : " (default) " ,
} ,
[FIX] migration: the cleanup installs its own module, and survives a refusal
Three defects, all mine, all found on a real run.
Creating a wizard could fail and leave the transaction ABORTED. The next
name read died on it, outside any guard, and the whole script stopped
with no report at all — only a traceback. Creating and reading the names
now happen inside the savepoint, and a report is printed whatever
happens: knowing what was done matters more than the trace of what
broke.
« No orphaned models found » is a UserError: the module signals the
EMPTY by raising. Counting it as a failure made a healthy database look
broken, four warnings out of five kinds.
The module was installed at step 3, after being used at step 2, so the
first run had no wizard at all and answered « nothing to do ». It is now
installed by the tool itself, and step 3 no longer asks to redo by hand
what has just been done automatically.
--- FR ---
[FIX] migration : le nettoyage pose son module, et survit à un refus
Trois défauts, tous à moi, tous trouvés sur une vraie exécution.
Créer un assistant pouvait échouer en laissant la transaction AVORTÉE.
La lecture de nom suivante mourait dessus, hors de tout garde, et le
script s'arrêtait sans aucun rapport — juste une trace. La création et
la lecture des noms sont désormais dans le point de reprise, et un
rapport est imprimé quoi qu'il arrive : savoir ce qui a été fait vaut
mieux que la trace de ce qui a cassé.
« No orphaned models found » est une UserError : le module signale le
VIDE en levant. Le compter comme un échec faisait passer une base saine
pour cassée, quatre avertissements sur cinq catégories.
Le module était installé à l'étape 3, après avoir servi à l'étape 2 : le
premier passage n'avait donc aucun assistant et répondait « rien à
faire ». L'outil le pose lui-même, et l'étape 3 ne demande plus de
refaire à la main ce qui vient d'être fait.
Assisted-by: Claude Opus 5
2026-08-17 17:45:04 -04:00
" database_cleanup is not installed on this base; " : {
" fr " : " database_cleanup n ' est pas installé sur cette base ; " ,
" en " : " database_cleanup is not installed on this base; " ,
} ,
" installing it first. " : {
" fr " : " installation préalable. " ,
" en " : " installing it first. " ,
} ,
" Could not install database_cleanup. " : {
" fr " : " Impossible d ' installer database_cleanup. " ,
" en " : " Could not install database_cleanup. " ,
} ,
[IMP] migration: say when the COW question is actually asked
An early check warns, hours before the bump, that a copy will break it. It
then says « arbitrate BEFORE launching the migration » and hands over a raw
UPDATE — and no question follows. Read on a resumed run it looks like the
question already went by and was skipped, which is exactly how it was read.
It now says the migration asks at the bump itself, and shows what each copy
holds before the answer. The manual UPDATE stays, but after the two commands
that do it reversibly: telling someone to hand-write SQL when --restore exists
is offering the sharper tool first.
--- FR ---
Un contrôle précoce prévient, des heures avant le palier, qu'une copie va le
casser. Il dit ensuite « arbitrez AVANT de lancer la migration » et livre un
UPDATE brut — et aucune question ne suit. Lu au cours d'une reprise, on croit
que la question est passée et a été sautée, ce qui est exactement la lecture
qui en a été faite.
Il dit maintenant que la migration pose la question au palier lui-même, en
montrant ce que chaque copie contient avant qu'on réponde. L'UPDATE manuel
reste, mais après les deux commandes qui le font de façon réversible : dire à
quelqu'un d'écrire du SQL à la main quand --restore existe, c'est tendre
l'outil le plus coupant en premier.
Assisted-by: Claude Opus 5
2026-08-12 01:36:47 -04:00
" Nothing to decide yet " : {
" fr " : " Rien à décider pour l ' instant " ,
" en " : " Nothing to decide yet " ,
} ,
" the migration will offer to neutralize these copies " : {
" fr " : " la migration proposera de neutraliser ces copies " ,
" en " : " the migration will offer to neutralize these copies " ,
} ,
" at the version bump itself, showing what each one holds. " : {
" fr " : " au moment du palier lui-même, en montrant ce que chacune "
" contient. " ,
" en " : " at the version bump itself, showing what each one holds. " ,
} ,
[ADD] migration: go back a step from a prompt, and name the DB
Realising at a prompt that an earlier step deserved another answer had one
way out: Ctrl+C. That leaves the progression as it stands and makes you find
the resume screen again to rewind. « b » does it properly — it shows the
steps, rewinds the state, writes it, and says what to relaunch.
Cancelling that must not stop the migration, which is the trap: it returns to
the same prompt, exactly where you were. Removing that guard makes two tests
fail.
The COW warning printed « -d DB -t odooXX.0 » for commands meant to be pasted.
It knows both values, so it prints them, and offers --shape as well since that
is half the answer.
Checked on the four outcomes: a normal answer passes through, « b » then a
step rewinds and stops, cancelling and an unknown step both continue. 15 tests.
--- FR ---
S'apercevoir à une invite qu'une étape antérieure méritait un autre choix
n'avait qu'une issue : Ctrl+C. Cela laisse la progression telle quelle et
oblige à retrouver l'écran de reprise pour rembobiner. « b » le fait
proprement — il montre les étapes, rembobine l'état, l'écrit, et dit quoi
relancer.
Y renoncer ne doit pas arrêter la migration, et c'est le piège : on revient à
la même invite, exactement là où l'on était. Retirer ce garde-fou fait tomber
deux tests.
L'avertissement COW affichait « -d DB -t odooXX.0 » pour des commandes faites
pour être collées. Il connaît les deux valeurs, donc il les écrit, et propose
aussi --shape puisque c'est la moitié de la réponse.
Vérifié sur les quatre issues : une réponse normale passe, « b » puis une
étape rembobine et arrête, annuler et un choix inconnu continuent. 15 tests.
Assisted-by: Claude Opus 5
2026-08-12 01:47:44 -04:00
" Press to continue " : {
" fr " : " Appuyez pour continuer " ,
" en " : " Press to continue " ,
} ,
" (b = go back to a previous step) " : {
" fr " : " (b = revenir à une étape précédente) " ,
" en " : " (b = go back to a previous step) " ,
} ,
" Neutralize database, press to continue " : {
" fr " : " Neutraliser la base, appuyez pour continuer " ,
" en " : " Neutralize database, press to continue " ,
} ,
" Replay from which step? (empty to cancel) " : {
" fr " : " Rejouer à partir de quelle étape ? (vide pour annuler) " ,
" en " : " Replay from which step? (empty to cancel) " ,
} ,
" Rewound. " : { " fr " : " Rembobiné. " , " en " : " Rewound. " } ,
" Relaunch the migration to resume from there. " : {
" fr " : " Relancez la migration pour reprendre à partir de là. " ,
" en " : " Relaunch the migration to resume from there. " ,
} ,
[ADD] migration: offer « go back to a step » on the resume screen
The screen could already replay from any step -- Enter on a row does it --
but that lived in a hint line under four named buttons. A capability you
have to guess is not offered, and it was reported as missing. It now has a
key, a button and a message, like the four others.
The b key added earlier went to the line-by-line prompts during the run,
not to this screen. Same letter on both, so going back is one key wherever
you are.
Two defects behind it. Replaying the chosen step actually SKIPPED it,
resuming at the next one. And a replayed step did not rewind the variables
it owns, so the second run started from the first one's leftovers.
Checked in a simulated terminal: b focuses the table, two arrows and Enter
return step 2, and the button focuses without choosing.
--- FR ---
L'écran savait déjà rejouer depuis n'importe quelle étape — Entrée sur une
rangée le fait — mais cela vivait dans une ligne d'indication sous quatre
boutons nommés. Une capacité qu'il faut deviner n'est pas offerte, et elle
a été signalée manquante. Elle a maintenant une touche, un bouton et un
message, comme les quatre autres.
La touche b ajoutée plus tôt s'adressait aux invites ligne à ligne pendant
l'exécution, pas à cet écran. Même lettre sur les deux : revenir en
arrière est une seule touche, où qu'on soit.
Deux défauts derrière cela. Rejouer l'étape choisie la SAUTAIT en fait,
reprenant à la suivante. Et une étape rejouée ne remettait pas à zéro les
variables qui lui appartiennent, si bien que la seconde exécution partait
des restes de la première.
Vérifié en terminal simulé : b donne le focus au tableau, deux flèches et
Entrée ramènent l'étape 2, et le bouton met le focus sans choisir.
Assisted-by: Claude Opus 5
2026-08-12 01:58:38 -04:00
" Go back to a step " : {
" fr " : " Revenir à une étape " ,
" en " : " Go back to a step " ,
} ,
" Choose a step, Enter replays from there. " : {
" fr " : " Choisissez une étape, Entrée rejoue à partir de là. " ,
" en " : " Choose a step, Enter replays from there. " ,
} ,
[ADD] analyse: inspect an Odoo database without restoring it
A read-only toolkit answering « what is in this database, and what will
break on a version bump »: schema size and tables no model claims,
customised views compared with their module source, and the x_ fields
Studio left behind. It reads a backup zip directly, so a 40 GB dump needs
no restore, and it reaches every answer from the TODO menu.
--- FR ---
Une boîte à outils en lecture seule qui répond à « qu'y a-t-il dans cette
base, et qu'est-ce qui cassera à la montée de version » : taille du schéma
et tables qu'aucun modèle ne réclame, vues personnalisées comparées à leur
source, champs x_ laissés par Studio. Elle lit un zip de sauvegarde tel
quel, donc sans restaurer, et tout s'atteint depuis le menu TODO.
Assisted-by: Claude Opus 5
2026-08-10 03:10:50 -04:00
# --- script/analyse : vues personnalisées ---
" Customised views " : {
" fr " : " Vues personnalisées " ,
" en " : " Customised views " ,
} ,
" Views " : { " fr " : " Vues " , " en " : " Views " } ,
" why " : { " fr " : " pourquoi " , " en " : " why " } ,
" more " : { " fr " : " de plus " , " en " : " more " } ,
" From an installed theme " : {
" fr " : " Posée par un thème installé " ,
" en " : " From an installed theme " ,
} ,
" Website copy (COW) " : {
" fr " : " Copie de site web (COW) " ,
" en " : " Website copy (COW) " ,
} ,
" Made with Studio " : { " fr " : " Faite avec Studio " , " en " : " Made with Studio " } ,
" Imported or exported " : {
" fr " : " Importée ou exportée " ,
" en " : " Imported or exported " ,
} ,
" Created from the interface " : {
" fr " : " Créée depuis l ' interface " ,
" en " : " Created from the interface " ,
} ,
" From a module, flagged as touched " : {
" fr " : " D ' un module, signalée comme retouchée " ,
" en " : " From a module, flagged as touched " ,
} ,
" Straight from a module " : {
" fr " : " Telle quelle depuis un module " ,
" en " : " Straight from a module " ,
} ,
" Every view comes straight from a module. " : {
" fr " : " Toutes les vues viennent telles quelles d ' un module. " ,
" en " : " Every view comes straight from a module. " ,
} ,
" Views that did not come straight from a module " : {
" fr " : " Vues qui ne viennent pas telles quelles d ' un module " ,
" en " : " Views that did not come straight from a module " ,
} ,
" Website copies are user data: Odoo copies a view instead of editing it. "
" Whether they will survive the next version is another question, and "
" these tools answer it: " : {
" fr " : " Les copies de site web sont des données utilisateur : Odoo "
" copie une vue au lieu de la modifier. Savoir si elles survivront à "
" la prochaine version est une autre question, et ces outils y "
" répondent : " ,
" en " : " Website copies are user data: Odoo copies a view instead of "
" editing it. Whether they will survive the next version is another "
" question, and these tools answer it: " ,
} ,
" Flags say a view was touched, not how. They are incomplete both ways: a "
" direct SQL write does not set arch_updated, and reset_arch clears it. "
" Comparing with the module source is what settles it. " : {
" fr " : " Les drapeaux disent qu ' une vue a été touchée, pas comment. Ils "
" sont incomplets dans les deux sens : un write SQL direct n ' arme pas "
" arch_updated, et reset_arch l ' efface. Seule la comparaison avec la "
" source du module tranche. " ,
" en " : " Flags say a view was touched, not how. They are incomplete "
" both ways: a direct SQL write does not set arch_updated, and "
" reset_arch clears it. Comparing with the module source is what "
" settles it. " ,
} ,
" List the views of an Odoo database that did not come straight from a "
" module, website copies included (read-only). " : {
" fr " : " Lister les vues d ' une base Odoo qui ne viennent pas telles "
" quelles d ' un module, copies de site web comprises (lecture seule). " ,
" en " : " List the views of an Odoo database that did not come straight "
" from a module, website copies included (read-only). " ,
} ,
" only show this category " : {
" fr " : " n ' afficher que cette catégorie " ,
" en " : " only show this category " ,
} ,
" how many views to show (default: 20) " : {
" fr " : " nombre de vues à afficher (défaut : 20) " ,
" en " : " how many views to show (default: 20) " ,
} ,
" list every view " : {
" fr " : " afficher toutes les vues " ,
" en " : " list every view " ,
} ,
" which views to compare (default: flagged) " : {
" fr " : " quelles vues comparer (défaut : celles qui sont signalées) " ,
" en " : " which views to compare (default: flagged) " ,
} ,
" From a module, silently drifted " : {
" fr " : " D ' un module, dérivée en silence " ,
" en " : " From a module, silently drifted " ,
} ,
" In --scope all, a difference is a lead, not a verdict: "
" read_arch_from_file returns the raw file, while the database holds the "
" arch AFTER load-time processing. Measured on a freshly installed 18.0 "
" database, 160 of its 974 views already differ this way. " : {
" fr " : " En --scope all, un écart est une piste, pas un verdict : "
" read_arch_from_file rend le fichier brut, alors que la base porte "
" l ' arch APRÈS traitement au chargement. Mesuré sur une base 18.0 "
" fraîchement installée, 160 de ses 974 vues diffèrent déjà ainsi. " ,
" en " : " In --scope all, a difference is a lead, not a verdict: "
" read_arch_from_file returns the raw file, while the database holds "
" the arch AFTER load-time processing. Measured on a freshly installed "
" 18.0 database, 160 of its 974 views already differ this way. " ,
} ,
" Compare with the module source? (Y/n): " : {
" fr " : " Comparer avec la source du module ? (O/n) : " ,
" en " : " Compare with the module source? (Y/n): " ,
} ,
# --- Menu Analyse : les actions « aller plus loin ». Ce qui était
# conseillé en options de ligne de commande est devenu des entrées :
# dire « utilisez -v » à quelqu'un qui est dans un menu, c'est lui
# demander d'en sortir pour obtenir ce que le menu pouvait offrir.
" Browse the differences (TUI) " : {
" fr " : " 🖥 Naviguer dans les écarts (plein écran) " ,
" en " : " 🖥 Browse the differences (TUI) " ,
} ,
" Compare every view (slower, noisier) " : {
" fr " : " 🔬 Comparer toutes les vues (plus long, plus bruyant) " ,
" en " : " 🔬 Compare every view (slower, noisier) " ,
} ,
" Compare first, then browse. " : {
" fr " : " Comparez d ' abord, vous naviguerez ensuite. " ,
" en " : " Compare first, then browse. " ,
} ,
" Compare the flagged views with the module source " : {
" fr " : " 🔍 Comparer les vues signalées avec la source du module " ,
" en " : " 🔍 Compare the flagged views with the module source " ,
} ,
" Count rows exactly (full scan) " : {
" fr " : " 🔢 Compter les lignes exactement (balayage complet) " ,
" en " : " 🔢 Count rows exactly (full scan) " ,
} ,
" Counting rows exactly, one scan per table… " : {
" fr " : " Comptage exact des lignes, un balayage par table… " ,
" en " : " Counting rows exactly, one scan per table… " ,
} ,
" Export as JSON " : { " fr " : " 💾 Exporter en JSON " , " en " : " 💾 Export as JSON " } ,
" Go further " : { " fr " : " 🔎 Aller plus loin " , " en " : " 🔎 Go further " } ,
" Loading the Odoo registry, this takes a moment… " : {
" fr " : " Chargement du registre Odoo, cela prend un moment… " ,
" en " : " Loading the Odoo registry, this takes a moment… " ,
} ,
" No view differs from its module source. " : {
" fr " : " Aucune vue ne diffère de la source de son module. " ,
" en " : " No view differs from its module source. " ,
} ,
" Show every table " : {
" fr " : " 📜 Afficher toutes les tables " ,
" en " : " 📜 Show every table " ,
} ,
" Show every view " : {
" fr " : " 📜 Afficher toutes les vues " ,
" en " : " 📜 Show every view " ,
} ,
" Written to: " : { " fr " : " Écrit dans : " , " en " : " Written to: " } ,
" Flags say a view was touched, not how: only comparing with the module "
" source settles it. " : {
" fr " : " Les drapeaux disent qu ' une vue a été touchée, pas comment : "
" seule la comparaison avec la source du module tranche. " ,
" en " : " Flags say a view was touched, not how: only comparing with the "
" module source settles it. " ,
} ,
" blocking " : { " fr " : " bloquants " , " en " : " blocking " } ,
" Studio and hand-made x_ fields " : {
" fr " : " 🧩 Champs x_ (Studio et faits à la main) " ,
" en " : " 🧩 Studio and hand-made x_ fields " ,
} ,
" Show every field " : {
" fr " : " 📜 Afficher tous les champs " ,
" en " : " 📜 Show every field " ,
} ,
" A database " : { " fr " : " 💾 Une base de données " , " en " : " 💾 A database " } ,
" A backup .zip, without restoring it " : {
" fr " : " 🗜 Une sauvegarde .zip, sans la restaurer " ,
" en " : " 🗜 A backup .zip, without restoring it " ,
} ,
" Path to the backup .zip (empty to cancel): " : {
" fr " : " Chemin de la sauvegarde .zip (vide pour annuler) : " ,
" en " : " Path to the backup .zip (empty to cancel): " ,
} ,
" No such file: " : { " fr " : " Fichier introuvable : " , " en " : " No such file: " } ,
" from a backup " : { " fr " : " depuis une sauvegarde " , " en " : " from a backup " } ,
" Odoo backup .zip to inspect, without restoring it " : {
" fr " : " sauvegarde Odoo .zip à examiner, sans la restaurer " ,
" en " : " Odoo backup .zip to inspect, without restoring it " ,
} ,
" Not an Odoo backup (no manifest.json): " : {
" fr " : " Pas une sauvegarde Odoo (aucun manifest.json) : " ,
" en " : " Not an Odoo backup (no manifest.json): " ,
} ,
" Cannot read the backup: " : {
" fr " : " Lecture impossible de la sauvegarde : " ,
" en " : " Cannot read the backup: " ,
} ,
" This backup holds no dump.sql: " : {
" fr " : " Cette sauvegarde ne contient aucun dump.sql : " ,
" en " : " This backup holds no dump.sql: " ,
} ,
# --- script/analyse : champs et modèles hors module ---
" Nothing declares these in a file, so no module will "
" recreate them. What a version upgrade keeps is what "
" someone carried over. " : {
" fr " : " Rien ne les déclare dans un fichier, donc aucun module "
" ne les recréera. Ce qu ' une montée de version conserve "
" est ce que quelqu ' un a reporté. " ,
" en " : " Nothing declares these in a file, so no module will "
" recreate them. What a version upgrade keeps is what "
" someone carried over. " ,
} ,
" A stored field without its column stops the registry "
" from loading, so the upgrade will not even start. Settle "
" these before anything else. " : {
" fr " : " Un champ stocké sans sa colonne empêche le registre de "
" charger, donc la montée de version ne démarrera même "
" pas. Réglez cela avant tout le reste. " ,
" en " : " A stored field without its column stops the registry "
" from loading, so the upgrade will not even start. Settle "
" these before anything else. " ,
} ,
" Use -v to list them all, --json for the raw data. " : {
" fr " : " Utilisez -v pour tout afficher, --json pour la donnée "
" brute. " ,
" en " : " Use -v to list them all, --json for the raw data. " ,
} ,
" List the fields and models added outside a module — "
" Studio or by hand (read-only). " : {
" fr " : " Lister les champs et modèles ajoutés hors module — "
" Studio ou faits à la main (lecture seule). " ,
" en " : " List the fields and models added outside a module — "
" Studio or by hand (read-only). " ,
} ,
" how many to show (default: 30) " : {
" fr " : " nombre à afficher (défaut : 30) " ,
" en " : " how many to show (default: 30) " ,
} ,
" list every one " : { " fr " : " tout afficher " , " en " : " list every one " } ,
" Studio " : { " fr " : " Studio " , " en " : " Studio " } ,
" Made by hand " : { " fr " : " Fait à la main " , " en " : " Made by hand " } ,
" Declared by a module " : {
" fr " : " Déclaré par un module " ,
" en " : " Declared by a module " ,
} ,
" stored, but its column is missing " : {
" fr " : " stocké, mais sa colonne manque " ,
" en " : " stored, but its column is missing " ,
} ,
" points at a model that no longer exists " : {
" fr " : " pointe vers un modèle qui n ' existe plus " ,
" en " : " points at a model that no longer exists " ,
} ,
" its model no longer exists " : {
" fr " : " son modèle n ' existe plus " ,
" en " : " its model no longer exists " ,
} ,
" its table could not be resolved " : {
" fr " : " sa table n ' a pas pu être résolue " ,
" en " : " its table could not be resolved " ,
} ,
" origin " : { " fr " : " provenance " , " en " : " origin " } ,
" Fields added outside a module " : {
" fr " : " Champs ajoutés hors module " ,
" en " : " Fields added outside a module " ,
} ,
" Custom fields " : { " fr " : " Champs personnalisés " , " en " : " Custom fields " } ,
" Custom models " : { " fr " : " Modèles personnalisés " , " en " : " Custom models " } ,
" No field or model was added outside a module. " : {
" fr " : " Aucun champ ni modèle n ' a été ajouté hors module. " ,
" en " : " No field or model was added outside a module. " ,
} ,
" Blocking " : { " fr " : " Bloquant " , " en " : " Blocking " } ,
" To carry over by hand " : {
" fr " : " À reporter à la main " ,
" en " : " To carry over by hand " ,
} ,
# --- script/analyse : comparaison et navigation des écarts ---
" Differences only " : {
" fr " : " Écarts seuls " ,
" en " : " Differences only " ,
} ,
" Ignore indentation " : {
" fr " : " Ignorer l ' indentation " ,
" en " : " Ignore indentation " ,
} ,
" Copy " : {
" fr " : " Copier " ,
" en " : " Copy " ,
} ,
" Reset command " : {
" fr " : " Commande de réinitialisation " ,
" en " : " Reset command " ,
} ,
" module (file) " : {
" fr " : " module (fichier) " ,
" en " : " module (file) " ,
} ,
" database " : {
" fr " : " base " ,
" en " : " database " ,
} ,
" Difference copied. " : {
" fr " : " Écart copié. " ,
" en " : " Difference copied. " ,
} ,
" No Odoo configuration file found. " : {
" fr " : " Aucun fichier de configuration Odoo trouvé. " ,
" en " : " No Odoo configuration file found. " ,
} ,
" odoo_bin.sh not found. " : {
" fr " : " odoo_bin.sh est introuvable. " ,
" en " : " odoo_bin.sh not found. " ,
} ,
" The Odoo shell returned no result: " : {
" fr " : " Le shell Odoo n ' a rien renvoyé : " ,
" en " : " The Odoo shell returned no result: " ,
} ,
" The Odoo shell exceeded the timeout (s): " : {
" fr " : " Le shell Odoo a dépassé le délai imparti (s) : " ,
" en " : " The Odoo shell exceeded the timeout (s): " ,
} ,
" Unreadable JSON from the Odoo shell: " : {
" fr " : " JSON illisible en sortie du shell Odoo : " ,
" en " : " Unreadable JSON from the Odoo shell: " ,
} ,
" To restore a view to what its module declares — this "
" WRITES to the database, so read the difference first: " : {
" fr " : " Pour rendre à une vue ce que déclare son module — ceci "
" ÉCRIT dans la base, lisez donc l ' écart d ' abord : " ,
" en " : " To restore a view to what its module declares — this "
" WRITES to the database, so read the difference first: " ,
} ,
" compare with the module source (opens an Odoo shell) " : {
" fr " : " comparer avec la source du module (ouvre un shell Odoo) " ,
" en " : " compare with the module source (opens an Odoo shell) " ,
} ,
" fail if the comparison could not be made " : {
" fr " : " échouer si la comparaison n ' a pas pu être faite " ,
" en " : " fail if the comparison could not be made " ,
} ,
" browse the differences in a full-screen view " : {
" fr " : " naviguer dans les écarts en plein écran " ,
" en " : " browse the differences in a full-screen view " ,
} ,
" Flags say a view was touched, not how. They are incomplete "
" both ways: a direct SQL write does not set arch_updated, "
" and reset_arch clears it. Comparing with the module source "
" is what settles it — add --diff. " : {
" fr " : " Les drapeaux disent qu ' une vue a été touchée, pas comment. "
" Ils sont incomplets dans les deux sens : un write SQL "
" direct n ' arme pas arch_updated, et reset_arch l ' efface. "
" Seule la comparaison avec la source du module tranche — "
" ajoutez --diff. " ,
" en " : " Flags say a view was touched, not how. They are incomplete "
" both ways: a direct SQL write does not set arch_updated, "
" and reset_arch clears it. Comparing with the module source "
" is what settles it — add --diff. " ,
} ,
" Database is Odoo " : {
" fr " : " La base est en Odoo " ,
" en " : " Database is Odoo " ,
} ,
" checkout is " : {
" fr " : " le checkout est en " ,
" en " : " checkout is " ,
} ,
" views were flagged but hold exactly what their module "
" declares: only the comparison could tell. " : {
" fr " : " vues étaient signalées mais portent exactement ce que "
" déclare leur module : seule la comparaison pouvait le "
" dire. " ,
" en " : " views were flagged but hold exactly what their module "
" declares: only the comparison could tell. " ,
} ,
" No reference arch, so nothing was compared: " : {
" fr " : " Aucune arch de référence, donc rien n ' a été comparé : " ,
" en " : " No reference arch, so nothing was compared: " ,
} ,
" Not a terminal: showing the text report instead. " : {
" fr " : " Pas un terminal : affichage du rapport texte à la place. " ,
" en " : " Not a terminal: showing the text report instead. " ,
} ,
" To restore this view to what its module declares: " : {
" fr " : " Pour rendre à cette vue ce que déclare son module : " ,
" en " : " To restore this view to what its module declares: " ,
} ,
# --- script/analyse : erreurs du socle ---
" Invalid database name: " : {
" fr " : " Nom de base invalide : " ,
" en " : " Invalid database name: " ,
} ,
" Cannot read from the database: " : {
" fr " : " Lecture impossible dans la base : " ,
" en " : " Cannot read from the database: " ,
} ,
" is not an Odoo database. " : {
" fr " : " n ' est pas une base Odoo. " ,
" en " : " is not an Odoo database. " ,
} ,
" psql is not installed or not in PATH. " : {
" fr " : " psql n ' est pas installé, ou absent du PATH. " ,
" en " : " psql is not installed or not in PATH. " ,
} ,
" Query exceeded the timeout (s): " : {
" fr " : " La requête a dépassé le délai imparti (s) : " ,
" en " : " Query exceeded the timeout (s): " ,
} ,
" Unreadable JSON from psql: " : {
" fr " : " JSON illisible en sortie de psql : " ,
" en " : " Unreadable JSON from psql: " ,
} ,
" shared library, nothing to run here. " : {
" fr " : " bibliothèque partagée, rien à lancer ici. " ,
" en " : " shared library, nothing to run here. " ,
} ,
" Runnable tools in this directory: " : {
" fr " : " Outils exécutables de ce répertoire : " ,
" en " : " Runnable tools in this directory: " ,
} ,
" No analysis tool here yet. " : {
" fr " : " Aucun outil d ' analyse ici pour l ' instant. " ,
" en " : " No analysis tool here yet. " ,
} ,
" From the menu: " : { " fr " : " Depuis le menu : " , " en " : " From the menu: " } ,
" in the dump " : { " fr " : " dans le dump " , " en " : " in the dump " } ,
" Weight in the dump " : {
" fr " : " Poids dans le dump " ,
" en " : " Weight in the dump " ,
} ,
# --- script/analyse : poids du schéma ---
" Schema analysis " : {
" fr " : " Analyse du schéma " ,
" en " : " Schema analysis " ,
} ,
" Database size " : {
" fr " : " Poids de la base " ,
" en " : " Database size " ,
} ,
" Tables " : { " fr " : " Tables " , " en " : " Tables " } ,
" Models " : { " fr " : " Modèles " , " en " : " Models " } ,
" rows " : { " fr " : " lignes " , " en " : " rows " } ,
" Models without table " : {
" fr " : " Modèles sans table " ,
" en " : " Models without table " ,
} ,
" abstract models have none, by design " : {
" fr " : " les modèles abstraits n ' en ont pas, par construction " ,
" en " : " abstract models have none, by design " ,
} ,
" ir_model_relation is absent, so m2m tables cannot be told apart from "
" orphans: the list below is unreliable. " : {
" fr " : " ir_model_relation est absente : impossible de distinguer une "
" table m2m d ' une orpheline, la liste ci-dessous n ' est pas fiable. " ,
" en " : " ir_model_relation is absent, so m2m tables cannot be told apart "
" from orphans: the list below is unreliable. " ,
} ,
" Heaviest tables " : {
" fr " : " Tables les plus lourdes " ,
" en " : " Heaviest tables " ,
} ,
" All tables, heaviest first " : {
" fr " : " Toutes les tables, de la plus lourde à la plus légère " ,
" en " : " All tables, heaviest first " ,
} ,
" use -v to list them all " : {
" fr " : " utilisez -v pour toutes les afficher " ,
" en " : " use -v to list them all " ,
} ,
" Every table belongs to an installed model. " : {
" fr " : " Chaque table appartient à un modèle installé. " ,
" en " : " Every table belongs to an installed model. " ,
} ,
" Orphan tables " : {
" fr " : " Tables orphelines " ,
" en " : " Orphan tables " ,
} ,
" No installed model claims these tables. They are usually left over from "
" modules uninstalled without DROP TABLE, and every version upgrade "
" carries them along. " : {
" fr " : " Aucun modèle installé ne réclame ces tables. Ce sont le plus "
" souvent des reliquats de modules désinstallés sans DROP TABLE, que "
" chaque montée de version transporte avec elle. " ,
" en " : " No installed model claims these tables. They are usually left "
" over from modules uninstalled without DROP TABLE, and every version "
" upgrade carries them along. " ,
} ,
" Check what they hold before dropping anything. " : {
" fr " : " Regardez ce qu ' elles contiennent avant de supprimer quoi que ce "
" soit. " ,
" en " : " Check what they hold before dropping anything. " ,
} ,
" Row counts are estimates from the last ANALYZE. Use --exact for real "
" counts, at the cost of one full scan per table. " : {
" fr " : " Les nombres de lignes sont estimés d ' après le dernier ANALYZE. "
" Utilisez --exact pour un comptage réel, au prix d ' un balayage "
" complet par table. " ,
" en " : " Row counts are estimates from the last ANALYZE. Use --exact for "
" real counts, at the cost of one full scan per table. " ,
} ,
" Report the size of an Odoo database and the tables no installed model "
" claims (read-only). " : {
" fr " : " Rapporter le poids d ' une base Odoo et les tables qu ' aucun "
" modèle installé ne réclame (lecture seule). " ,
" en " : " Report the size of an Odoo database and the tables no installed "
" model claims (read-only). " ,
} ,
" database to inspect " : {
" fr " : " base à examiner " ,
" en " : " database to inspect " ,
} ,
" count rows exactly: one full scan per table " : {
" fr " : " compter les lignes exactement : un balayage complet par table " ,
" en " : " count rows exactly: one full scan per table " ,
} ,
" how many tables to show (default: 20) " : {
" fr " : " nombre de tables à afficher (défaut : 20) " ,
" en " : " how many tables to show (default: 20) " ,
} ,
" list every table " : {
" fr " : " afficher toutes les tables " ,
" en " : " list every table " ,
} ,
" output JSON " : { " fr " : " sortie JSON " , " en " : " output JSON " } ,
" path to an Odoo config file " : {
" fr " : " chemin d ' un fichier de configuration Odoo " ,
" en " : " path to an Odoo config file " ,
} ,
2026-08-10 03:10:50 -04:00
# Courriel
" mail_menu " : {
" fr " : " Courriel - Lire et envoyer du courriel " ,
" en " : " Mail - Read and send email " ,
} ,
" mail_ai_question " : {
" fr " : " Question IA - Poser une question à un modèle " ,
" en " : " AI question - Ask a model a question " ,
} ,
" mail_open_tui " : {
" fr " : " Ouvrir le client courriel (TUI) " ,
" en " : " Open the mail client (TUI) " ,
} ,
" mail_accounts_menu " : { " fr " : " Comptes " , " en " : " Accounts " } ,
" mail_sync_now " : {
" fr " : " Synchroniser maintenant " ,
" en " : " Synchronise now " ,
} ,
" mail_cache_menu " : { " fr " : " Cache " , " en " : " Cache " } ,
" mail_account_list " : { " fr " : " Lister les comptes " , " en " : " List accounts " } ,
" mail_account_add " : { " fr " : " Ajouter un compte " , " en " : " Add an account " } ,
" mail_account_delete " : {
" fr " : " Supprimer un compte " ,
" en " : " Delete an account " ,
} ,
" mail_account_template " : {
" fr " : " Générer un modèle accounts.json " ,
" en " : " Generate an accounts.json template " ,
} ,
" mail_account_test " : {
" fr " : " Tester la connexion d ' un compte " ,
" en " : " Test an account connection " ,
} ,
" mail_cache_default_mode " : {
" fr " : " Mode de cache par défaut " ,
" en " : " Default cache mode " ,
} ,
" mail_cache_account_mode " : {
" fr " : " Mode de cache d ' un compte " ,
" en " : " Cache mode of one account " ,
} ,
" mail_cache_size_purge " : {
" fr " : " Taille du cache et purge " ,
" en " : " Cache size and purge " ,
} ,
" mail_no_account " : {
" fr " : " Aucun compte configuré. Ajoutez-en un d ' abord. " ,
" en " : " No account configured. Add one first. " ,
} ,
" mail_ask_name " : {
" fr " : " Nom court du compte : " ,
" en " : " Short account name: " ,
} ,
" mail_ask_email " : { " fr " : " Adresse courriel : " , " en " : " Email address: " } ,
" mail_ask_display_name " : {
" fr " : " Nom affiché (facultatif) : " ,
" en " : " Display name (optional): " ,
} ,
" mail_ask_preset " : { " fr " : " Fournisseur : " , " en " : " Provider: " } ,
" mail_ask_password " : { " fr " : " Mot de passe : " , " en " : " Password: " } ,
" mail_ask_imap_host " : { " fr " : " Serveur IMAP : " , " en " : " IMAP server: " } ,
" mail_ask_smtp_host " : { " fr " : " Serveur SMTP : " , " en " : " SMTP server: " } ,
" mail_ask_account " : { " fr " : " Quel compte ? " , " en " : " Which account? " } ,
" mail_ask_mode " : {
" fr " : " Mode (clear / encrypted / ephemeral) : " ,
" en " : " Mode (clear / encrypted / ephemeral): " ,
} ,
" mail_app_password_note " : {
" fr " : " Ce fournisseur exige un mot de passe d ' application. " ,
" en " : " This provider requires an app password. " ,
} ,
" mail_account_saved " : { " fr " : " Compte enregistré. " , " en " : " Account saved. " } ,
" mail_account_save " : { " fr " : " Enregistrer " , " en " : " Save " } ,
" mail_account_missing_fields " : {
" fr " : " Nom, adresse et mot de passe sont requis. " ,
" en " : " Name, address and password are required. " ,
} ,
" mail_account_add_unavailable " : {
" fr " : " Ajout de compte indisponible dans ce contexte. " ,
" en " : " Adding an account is unavailable in this context. " ,
} ,
" mail_account_deleted " : {
" fr " : " Compte supprimé. " ,
" en " : " Account deleted. " ,
} ,
" mail_connection_ok " : {
" fr " : " Connexion réussie. " ,
" en " : " Connection succeeded. " ,
} ,
" mail_connection_failed " : {
" fr " : " Connexion échouée : " ,
" en " : " Connection failed: " ,
} ,
" mail_template_written " : {
" fr " : " Modèle écrit dans " ,
" en " : " Template written to " ,
} ,
" mail_purge_confirm " : {
" fr " : " Effacer tout le cache de ce compte ? (o/N) " ,
" en " : " Erase this account ' s whole cache? (y/N) " ,
} ,
" mail_purged " : { " fr " : " Cache effacé. " , " en " : " Cache erased. " } ,
" mail_no_vault " : {
" fr " : " Aucun coffre disponible : installez pykeepass ou déverrouillez un trousseau système. " ,
" en " : " No vault available: install pykeepass or unlock a system keyring. " ,
} ,
2026-08-10 03:10:50 -04:00
# Coffre KeePass, côté infrastructure partagée (`kdbx_manager`) : ces
# messages servent à TOUT le CLI, pas seulement au courriel.
" kdbx_vault_is " : {
" fr " : " Coffre KeePass : " ,
" en " : " KeePass vault: " ,
} ,
" kdbx_ask_password " : {
" fr " : " Mot de passe du coffre (vide pour abandonner) : " ,
" en " : " Vault password (empty to give up): " ,
} ,
" kdbx_wrong_password " : {
" fr " : " Mot de passe incorrect pour ce coffre KeePass. " ,
" en " : " Wrong password for this KeePass vault. " ,
} ,
" kdbx_give_up " : {
" fr " : " Coffre non ouvert : on abandonne. " ,
" en " : " Vault not opened: giving up. " ,
} ,
2026-08-10 03:10:50 -04:00
" mail_kdbx_none_configured " : {
" fr " : " Aucun fichier kdbx n ' est configuré. " ,
" en " : " No kdbx file is configured. " ,
} ,
" mail_kdbx_menu_create " : {
" fr " : " Créer un nouveau fichier .kdbx " ,
" en " : " Create a new .kdbx file " ,
} ,
" mail_kdbx_menu_choose " : {
" fr " : " Choisir un fichier existant " ,
" en " : " Choose an existing file " ,
} ,
" mail_kdbx_menu_cancel " : { " fr " : " Annuler " , " en " : " Cancel " } ,
" mail_kdbx_ask_choice " : { " fr " : " Votre choix : " , " en " : " Your choice: " } ,
" mail_kdbx_ask_path_new " : {
" fr " : " Chemin du nouveau fichier kdbx " ,
" en " : " Path for the new kdbx file " ,
} ,
" mail_kdbx_ask_path_existing " : {
" fr " : " Chemin du fichier kdbx existant : " ,
" en " : " Path to the existing kdbx file: " ,
} ,
" mail_kdbx_ask_password " : {
" fr " : " Mot de passe du coffre : " ,
" en " : " Vault password: " ,
} ,
" mail_kdbx_ask_password_confirm " : {
" fr " : " Confirmez le mot de passe : " ,
" en " : " Confirm the password: " ,
} ,
" mail_kdbx_password_mismatch " : {
" fr " : " Les mots de passe ne correspondent pas. " ,
" en " : " Passwords do not match. " ,
} ,
" mail_kdbx_path_not_found " : {
" fr " : " Ce fichier n ' existe pas : " ,
" en " : " This file does not exist: " ,
} ,
" mail_kdbx_created " : {
" fr " : " Fichier kdbx créé : " ,
" en " : " Kdbx file created: " ,
} ,
" mail_kdbx_path_recorded " : {
" fr " : " Fichier kdbx configuré : " ,
" en " : " Kdbx file configured: " ,
} ,
" mail_no_password_stored " : {
" fr " : " Aucun mot de passe enregistré pour ce compte. " ,
" en " : " No password stored for this account. " ,
} ,
" mail_install_textual " : {
" fr " : " Installez textual pour le client courriel (pip). " ,
" en " : " Install textual for the mail client (pip). " ,
} ,
" mail_accounts " : { " fr " : " Comptes " , " en " : " Accounts " } ,
" mail_search " : { " fr " : " Rechercher… " , " en " : " Search… " } ,
" mail_search_clear " : {
" fr " : " Effacer la recherche " ,
" en " : " Clear search " ,
} ,
" mail_from " : { " fr " : " De : " , " en " : " From: " } ,
" mail_to " : { " fr " : " À : " , " en " : " To: " } ,
" mail_cc " : { " fr " : " Cc : " , " en " : " Cc: " } ,
" mail_subject " : { " fr " : " Objet : " , " en " : " Subject: " } ,
" mail_date " : { " fr " : " Date " , " en " : " Date " } ,
" mail_send " : { " fr " : " Envoyer " , " en " : " Send " } ,
" mail_attachments " : { " fr " : " Pièces jointes : " , " en " : " Attachments: " } ,
" mail_attachments_paths " : {
" fr " : " Pièces jointes (chemins séparés par des points-virgules) " ,
" en " : " Attachments (semicolon-separated paths) " ,
} ,
" mail_browse " : { " fr " : " Parcourir… " , " en " : " Browse… " } ,
" mail_browse_failed " : {
" fr " : " Sélecteur de fichiers impossible : " ,
" en " : " File browser unavailable: " ,
} ,
" mail_no_subject " : { " fr " : " (sans objet) " , " en " : " (no subject) " } ,
" mail_body_needs_network " : {
" fr " : " Corps non téléchargé — connexion requise. " ,
" en " : " Body not downloaded — connection required. " ,
} ,
" mail_body_error " : {
" fr " : " Lecture du corps impossible : " ,
" en " : " Cannot read the body: " ,
} ,
" mail_flag_error " : {
" fr " : " Drapeau non transmis au serveur : " ,
" en " : " Flag not sent to the server: " ,
} ,
" mail_syncing " : { " fr " : " Synchronisation de " , " en " : " Synchronising " } ,
" mail_new_messages " : { " fr " : " nouveaux messages " , " en " : " new messages " } ,
" mail_errors " : { " fr " : " erreurs " , " en " : " errors " } ,
" mail_folders_resynced " : {
" fr " : " dossiers resynchronisés (UIDVALIDITY changé) : " ,
" en " : " folders resynchronised (UIDVALIDITY changed): " ,
} ,
" mail_offline_cannot_send " : {
" fr " : " Compte hors ligne : envoi impossible. " ,
" en " : " Account offline: cannot send. " ,
} ,
" mail_sent_to " : { " fr " : " Envoyé à " , " en " : " Sent to " } ,
" mail_sent_not_filed " : {
" fr " : " envoyé, mais pas classé dans Envoyés " ,
" en " : " sent, but not filed in Sent " ,
} ,
" mail_nothing_to_reply_to " : {
" fr " : " Aucun message sélectionné. " ,
" en " : " No message selected. " ,
} ,
" mail_nothing_to_forward " : {
" fr " : " Aucun message à transférer. " ,
" en " : " No message to forward. " ,
} ,
" mail_attachment_not_found " : {
" fr " : " Pièce jointe introuvable. " ,
" en " : " Attachment not found. " ,
} ,
" mail_no_attachment " : {
" fr " : " Ce message n ' a pas de pièce jointe. " ,
" en " : " This message has no attachment. " ,
} ,
" mail_save_failed " : {
" fr " : " Enregistrement impossible : " ,
" en " : " Cannot save: " ,
} ,
" mail_saved_to " : { " fr " : " Enregistré dans " , " en " : " Saved to " } ,
" mail_log_binding " : { " fr " : " Journal " , " en " : " Log " } ,
" mail_log_close " : { " fr " : " Fermer " , " en " : " Close " } ,
" mail_log_tail_heading " : {
" fr " : " Journal (fin) : " ,
" en " : " Log (tail): " ,
} ,
" mail_log_errors_heading " : {
" fr " : " Erreurs de synchronisation (session en cours) : " ,
" en " : " Sync errors (current session): " ,
} ,
" mail_log_missing " : {
" fr " : " journal introuvable " ,
" en " : " log not found " ,
} ,
" mail_log_empty " : { " fr " : " journal vide " , " en " : " log empty " } ,
" mail_log_unreadable " : {
" fr " : " journal illisible " ,
" en " : " log unreadable " ,
} ,
" mail_log_no_errors " : {
" fr " : " Aucune erreur de synchronisation dans cette session. " ,
" en " : " No sync errors in this session. " ,
} ,
" mail_layout_binding " : { " fr " : " Vue " , " en " : " View " } ,
" mail_layout_switched " : { " fr " : " Disposition : " , " en " : " Layout: " } ,
" mail_layout_columns " : { " fr " : " Colonnes " , " en " : " Columns " } ,
" mail_layout_split " : { " fr " : " Partagée " , " en " : " Split " } ,
" mail_layout_stacked " : { " fr " : " Empilée " , " en " : " Stacked " } ,
" mail_pane_grow_binding " : { " fr " : " Agrandir volet " , " en " : " Grow pane " } ,
" mail_pane_shrink_binding " : {
" fr " : " Rétrécir volet " ,
" en " : " Shrink pane " ,
} ,
" mail_pane_reset_binding " : {
" fr " : " Tailles par défaut " ,
" en " : " Reset sizes " ,
} ,
" mail_pane_reset_done " : {
" fr " : " Tailles des volets réinitialisées. " ,
" en " : " Pane sizes reset. " ,
} ,
" mail_pane_splitter_tooltip " : {
" fr " : " Glisser pour redimensionner " ,
" en " : " Drag to resize " ,
} ,
" mail_fullscreen_binding " : { " fr " : " Plein écran " , " en " : " Full screen " } ,
# -- Libellés des raccourcis de MailApp (suffixe _binding) -------------
# Ce sont les descriptions des `Binding` de `MailApp` : elles s'affichent
# au pied d'écran ET, depuis la tâche 26, dans la fenêtre d'aide (`h`),
# qui les lit directement dans `MailApp.BINDINGS`. Le français est repris
# MOT POUR MOT de ce qui était écrit en dur avant cette tâche — le pied
# d'écran d'un utilisateur francophone ne change pas.
" mail_quit_binding " : { " fr " : " Quitter " , " en " : " Quit " } ,
" mail_sync_current_binding " : { " fr " : " Sync " , " en " : " Sync " } ,
" mail_sync_all_binding " : { " fr " : " Sync tout " , " en " : " Sync all " } ,
" mail_back_binding " : { " fr " : " Retour " , " en " : " Back " } ,
" mail_search_binding " : { " fr " : " Rechercher " , " en " : " Search " } ,
" mail_mark_seen_binding " : { " fr " : " Lu " , " en " : " Read " } ,
" mail_mark_unseen_binding " : { " fr " : " Non lu " , " en " : " Unread " } ,
" mail_save_attachment_binding " : {
" fr " : " Enregistrer PJ " ,
" en " : " Save attachment " ,
} ,
" mail_compose_binding " : { " fr " : " Écrire " , " en " : " Compose " } ,
" mail_reply_binding " : { " fr " : " Répondre " , " en " : " Reply " } ,
" mail_reply_all_binding " : { " fr " : " Répondre à tous " , " en " : " Reply all " } ,
" mail_forward_binding " : { " fr " : " Transférer " , " en " : " Forward " } ,
" mail_add_account_binding " : {
" fr " : " Nouveau compte " ,
" en " : " New account " ,
} ,
# -- Fenêtre d'aide (touche h) -----------------------------------------
# La liste des touches n'est PAS ici : elle est engendrée depuis
# `MailApp.BINDINGS` (voir `HelpScreen`), avec les libellés ci-dessus.
# Seul ce qu'une liste de touches ne peut pas dire est rédigé ici.
" mail_help_binding " : { " fr " : " Aide " , " en " : " Help " } ,
" mail_help_close " : { " fr " : " Fermer " , " en " : " Close " } ,
" mail_help_title " : {
" fr " : " Aide — client courriel " ,
" en " : " Help — mail client " ,
} ,
" mail_help_keys_heading " : {
" fr " : " Raccourcis clavier : " ,
" en " : " Keyboard shortcuts: " ,
} ,
" mail_help_notes_heading " : { " fr " : " Bon à savoir : " , " en " : " Good to know: " } ,
" mail_help_mouse " : {
" fr " : (
" Souris : glisser une barre entre deux volets les redimensionne. "
" Au clavier, + et - font de même sur le volet qui a le focus, et "
" 0 remet les tailles par défaut. Les tailles sont retenues par "
" disposition. "
) ,
" en " : (
" Mouse: drag a bar between two panes to resize them. From the "
" keyboard, + and - do the same to the focused pane, and 0 resets "
" the sizes. Sizes are remembered per layout. "
) ,
} ,
" mail_help_layouts " : {
" fr " : (
" Dispositions : v passe de colonnes à partagée, puis empilée, puis "
" revient à colonnes. "
) ,
" en " : (
" Layouts: v cycles columns, split, stacked, then back to columns. "
) ,
} ,
" mail_help_sync " : {
" fr " : (
" Synchronisation : r synchronise le compte du dossier sélectionné "
" (tous ses dossiers), R synchronise tous les comptes. La "
" synchronisation automatique ne tourne QUE tant que le client est "
" ouvert, à l ' intervalle mail_refresh_sec (300 s par défaut ; 0 la "
" désactive). "
) ,
" en " : (
" Sync: r syncs the account of the selected folder (all its "
" folders), R syncs every account. The automatic refresh runs ONLY "
" while the client is open, at the mail_refresh_sec interval (300 s "
" by default; 0 disables it). "
) ,
} ,
" mail_help_files " : {
" fr " : (
" Fichiers : le journal est dans ~/.erplibre/mail.log (la touche l "
" en montre la fin), les comptes dans "
" ~/.erplibre/mail/accounts.json. Les mots de passe n ' y sont JAMAIS "
" écrits : ils vivent dans le coffre kdbx ou le trousseau du "
" système. "
) ,
" en " : (
" Files: the log lives in ~/.erplibre/mail.log (the l key shows its "
" tail), the accounts in ~/.erplibre/mail/accounts.json. Passwords "
" are NEVER written there: they live in the kdbx vault or in the "
" system keyring. "
) ,
} ,
" mail_help_close_hint " : {
" fr " : " Échap ferme cette fenêtre. " ,
" en " : " Esc closes this window. " ,
} ,
# -- Exceptions internes au paquet courriel (préfixe mail_err_) --------
# Label traduit, données dynamiques (chemins, texte serveur, valeurs de
# config) concaténées crues : jamais de traduction d'un message serveur
# ou d'un chemin de fichier.
#
# Quelques messages ont la donnée dynamique AU MILIEU de la phrase : ils
# sont donc assemblés à partir de deux clés (voire trois), dans un ordre
# FIXE codé au site d'appel plutôt que par une seule clé avec un
# emplacement — mail_err_unknown_security + mail_err_expected,
# mail_err_key_wrong_length + mail_err_octets_unit,
# mail_err_mode_prefix + mail_err_mode_requires_key(_no_vault),
# mail_err_imap_connection_prefix / mail_err_smtp_connection_prefix +
# mail_err_connection_refused_suffix, et mail_err_keyring_plaintext +
# mail_err_keyring_plaintext_hint. L'ordre des mots vit donc dans le
# code, pas dans les chaînes traduisibles : une langue à l'ordre des
# mots différent devra remplacer ces paires par une convention à
# emplacement (ex. `.format()`), pas par une simple concaténation.
" mail_err_envelope_too_short " : {
" fr " : " enveloppe trop courte " ,
" en " : " envelope too short " ,
} ,
" mail_err_sealed_in_clear_mode " : {
" fr " : " donnée chiffrée lue en mode clair : la clé du compte manque " ,
" en " : " encrypted data read in clear mode: the account key is missing " ,
} ,
" mail_err_unknown_envelope " : {
" fr " : " enveloppe inconnue : " ,
" en " : " unknown envelope: " ,
} ,
" mail_err_key_wrong_length " : {
" fr " : " la clé doit faire " ,
" en " : " the key must be " ,
} ,
" mail_err_octets_unit " : { " fr " : " octets " , " en " : " bytes " } ,
" mail_err_cryptography_not_installed " : {
" fr " : " le paquet cryptography n ' est pas installé " ,
" en " : " the cryptography package is not installed " ,
} ,
" mail_err_decrypt_refused " : {
" fr " : " déchiffrement refusé : clé fausse ou donnée altérée " ,
" en " : " decryption refused: wrong key or corrupted data " ,
} ,
" mail_err_envelope_unreadable " : {
" fr " : " enveloppe illisible : " ,
" en " : " unreadable envelope: " ,
} ,
" mail_err_mode_prefix " : { " fr " : " le mode " , " en " : " mode " } ,
" mail_err_mode_requires_key " : {
" fr " : " exige une clé " ,
" en " : " requires a key " ,
} ,
" mail_err_unknown_cache_mode " : {
" fr " : " mode de cache inconnu : " ,
" en " : " unknown cache mode: " ,
} ,
" mail_err_file_already_exists " : {
" fr " : " le fichier existe déjà : " ,
" en " : " the file already exists: " ,
} ,
" mail_err_invalid_secret_ref " : {
" fr " : " référence de secret invalide : " ,
" en " : " invalid secret reference: " ,
} ,
# Notes par fournisseur. Affichées à l'ajout d'un compte ET juste avant
# de redemander un mot de passe après un refus : elles doivent donner
# l'adresse EXACTE, pas un chemin de menu — Google et Apple déplacent
# régulièrement ces pages, et Google cache la sienne.
" mail_preset_note_gmail " : {
" fr " : (
" Générez-le sur https://myaccount.google.com/apppasswords "
" (16 caractères, les espaces sont acceptés). La validation en "
" deux étapes doit être active, sinon la page est vide. "
) ,
" en " : (
" Generate one at https://myaccount.google.com/apppasswords "
" (16 characters, spaces are accepted). Two-step verification "
" must be on, otherwise the page is empty. "
) ,
} ,
" mail_preset_note_outlook " : {
" fr " : (
" Générez-le sur https://account.microsoft.com/security. "
" Microsoft ferme l ' authentification simple sur les comptes "
" grand public : sans mot de passe d ' application, il faudra "
" OAuth (phase 2, non implémentée). "
) ,
" en " : (
" Generate one at https://account.microsoft.com/security. "
" Microsoft is closing basic authentication on consumer "
" accounts: without an app password this needs OAuth (phase 2, "
" not implemented). "
) ,
} ,
" mail_preset_note_icloud " : {
" fr " : (
" Générez-le sur https://account.apple.com, section « Connexion "
" et sécurité ». L ' authentification à deux facteurs doit être "
" active. "
) ,
" en " : (
' Generate one at https://account.apple.com, under " Sign-In and '
' Security " . Two-factor authentication must be on. '
) ,
} ,
" mail_preset_note_generic " : {
" fr " : " Saisissez les serveurs de votre fournisseur. " ,
" en " : " Enter your provider ' s servers. " ,
} ,
" mail_ask_app_password " : {
" fr " : " Mot de passe d ' application : " ,
" en " : " App password: " ,
} ,
2026-08-10 03:10:50 -04:00
" mail_err_password_not_ascii " : {
" fr " : (
" le mot de passe contient un caractère non ASCII, que ce client "
" IMAP ne sait pas transmettre — il n ' a pas été envoyé au "
" serveur "
) ,
" en " : (
" the password contains a non-ASCII character this IMAP client "
" cannot transmit — it was never sent to the server "
) ,
} ,
2026-08-10 03:10:50 -04:00
" mail_err_no_kdbx_configured " : {
" fr " : " aucun fichier kdbx configuré " ,
" en " : " no kdbx file configured " ,
} ,
" mail_err_kdbx_unreadable " : {
" fr " : " le fichier kdbx n ' a pas pu être ouvert " ,
" en " : " the kdbx file could not be opened " ,
} ,
" mail_err_no_vault_available " : {
" fr " : " aucun coffre disponible : ni kdbx, ni trousseau système " ,
" en " : " no vault available: neither kdbx nor system keyring " ,
} ,
" mail_err_keyring_plaintext " : {
" fr " : (
" le trousseau du système écrirait le mot de passe en clair "
" (backend "
) ,
" en " : (
" the system keyring would store the password in plaintext "
" (backend "
) ,
} ,
" mail_err_keyring_plaintext_hint " : {
" fr " : " Utilisez un fichier kdbx, ou déverrouillez un vrai trousseau. " ,
" en " : " Use a kdbx file, or unlock a real keyring. " ,
} ,
" mail_err_unknown_security " : {
" fr " : " sécurité inconnue : " ,
" en " : " unknown security: " ,
} ,
" mail_err_expected " : { " fr " : " (attendu " , " en " : " (expected " } ,
" mail_err_account_needs_name " : {
" fr " : " un compte doit avoir un nom " ,
" en " : " an account must have a name " ,
} ,
" mail_err_invalid_account_name " : {
" fr " : " nom de compte invalide : " ,
" en " : " invalid account name: " ,
} ,
" mail_err_account_name_reason " : {
" fr " : " (il sert de nom de dossier et de référence de coffre) " ,
" en " : " (it is used as a folder name and vault reference) " ,
} ,
" mail_err_account_unreadable " : {
" fr " : " compte illisible : " ,
" en " : " unreadable account: " ,
} ,
" mail_err_unknown_preset " : {
" fr " : " préréglage inconnu : " ,
" en " : " unknown preset: " ,
} ,
" mail_err_not_valid_json " : {
" fr " : " n ' est pas du JSON valide : " ,
" en " : " is not valid JSON: " ,
} ,
" mail_err_should_contain_json_object " : {
" fr " : " devrait contenir un objet JSON " ,
" en " : " should contain a JSON object " ,
} ,
" mail_err_duplicate_account_names " : {
" fr " : " noms de compte en double : " ,
" en " : " duplicate account names: " ,
} ,
" mail_err_already_exists_relaunch " : {
" fr " : " existe déjà — relancez avec l ' option de remplacement " ,
" en " : " already exists — rerun with the overwrite option " ,
} ,
" mail_err_symlink_refused " : {
" fr " : " est un lien symbolique : cache refusé " ,
" en " : " is a symlink: cache refused " ,
} ,
" mail_err_owned_by_other_user " : {
" fr " : " appartient à un autre utilisateur : cache refusé " ,
" en " : " belongs to another user: cache refused " ,
} ,
" mail_err_cache_unreadable " : {
" fr " : " cache illisible, purgez-le et resynchronisez : " ,
" en " : " unreadable cache, purge it and resynchronise: " ,
} ,
" mail_err_mode_requires_key_no_vault " : {
" fr " : " exige une clé : aucun coffre fourni " ,
" en " : " requires a key: no vault provided " ,
} ,
" mail_err_cache_not_open " : {
" fr " : " cache non ouvert : appelez open() d ' abord " ,
" en " : " cache not open: call open() first " ,
} ,
" mail_err_unknown_folder_fields " : {
" fr " : " champs de dossier inconnus : " ,
" en " : " unknown folder fields: " ,
} ,
" mail_err_unterminated_ampersand " : {
" fr " : " séquence & non terminée " ,
" en " : " unterminated & sequence " ,
} ,
" mail_err_server_replied " : {
" fr " : " : le serveur a répondu " ,
" en " : " : the server replied " ,
} ,
" mail_err_no_body_for_uid " : {
" fr " : " aucun corps rendu pour l ' UID " ,
" en " : " no body returned for UID " ,
} ,
" mail_err_imap_connection_prefix " : {
" fr " : " connexion IMAP à " ,
" en " : " IMAP connection to " ,
} ,
" mail_err_smtp_connection_prefix " : {
" fr " : " connexion SMTP à " ,
" en " : " SMTP connection to " ,
} ,
" mail_err_connection_refused_suffix " : {
" fr " : " refusée : " ,
" en " : " refused: " ,
} ,
" mail_err_message_needs_recipient " : {
" fr " : " un message doit avoir au moins un destinataire " ,
" en " : " a message must have at least one recipient " ,
} ,
" mail_err_attachment_missing " : {
" fr " : " pièce jointe introuvable : " ,
" en " : " attachment not found: " ,
} ,
" mail_err_no_recipient_nothing_sent " : {
" fr " : " aucun destinataire : rien n ' a été envoyé " ,
" en " : " no recipient: nothing was sent " ,
} ,
" mail_err_send_refused " : { " fr " : " envoi refusé : " , " en " : " send refused: " } ,
[ADD] todo qemu: offer PyCharm, Android Studio and GNOME extensions
A graphical VM was a desktop and nothing else: every developer tool had to
be installed by hand afterwards. A check list now carries them, filtered
per machine — Android Studio is x86_64 only, Google publishes no Linux
aarch64 build — and their disk cost reaches the plan before any qcow2 is
created. They are installed BEFORE the clone: PyCharm writes the .idea/ of
the repository, and the install that follows is what runs
pycharm_configuration.py, through update_env_version.pycharm_update().
Its launcher is named studio, which is enough to conclude the install
failed; it now answers to android-studio too. GNOME extensions come from
the site by UUID, per running Shell: the same endpoint serves gTile v59
for GNOME 46 and v62 for 48. Checked with stubs: every tool can fail and
the install's exit code still wins, 45 tests.
--- FR ---
Une VM graphique n'était qu'un bureau : chaque outil de développement
restait à poser à la main. Une liste à cocher les porte, filtrés machine
par machine — Android Studio n'existe qu'en x86_64, Google ne publiant
aucune archive Linux aarch64 — et leur place disque atteint le plan avant
qu'un seul qcow2 ne soit créé. Ils sont posés AVANT le clone : PyCharm
écrit le .idea/ du dépôt, et c'est l'installation qui suit qui lance
pycharm_configuration.py, via update_env_version.pycharm_update().
Son lanceur s'appelle studio, ce qui suffit à conclure à un échec ; il
répond désormais aussi à android-studio. Les extensions GNOME viennent du
site par UUID, selon le Shell qui tourne : le même point d'entrée sert
gTile v59 pour GNOME 46 et v62 pour 48. Vérifié avec des leurres : chaque
outil peut échouer sans que le code de sortie de l'installation ne change,
45 tests.
Assisted-by: Claude Opus 5
2026-08-17 22:57:56 -04:00
# QEMU — outils de développement des VM graphiques
" Python IDE, ERPLibre project pre-configured " : {
" fr " : " IDE Python, projet ERPLibre pré-configuré " ,
" en " : " Python IDE, ERPLibre project pre-configured " ,
} ,
" ERPLibre mobile development (x86_64 only) " : {
" fr " : " développement mobile ERPLibre (x86_64 seulement) " ,
" en " : " ERPLibre mobile development (x86_64 only) " ,
} ,
" GNOME extensions " : {
" fr " : " Extensions GNOME " ,
" en " : " GNOME extensions " ,
} ,
" suggested extensions + extension manager " : {
" fr " : " extensions suggérées + gestionnaire d ' extensions " ,
" en " : " suggested extensions + extension manager " ,
} ,
" Development tools for the graphical VMs: " : {
" fr " : " Outils de développement des VM graphiques : " ,
" en " : " Development tools for the graphical VMs: " ,
} ,
" Development tools (graphical VMs): " : {
" fr " : " Outils de développement (VM graphiques) : " ,
" en " : " Development tools (graphical VMs): " ,
} ,
" Development tools: " : {
" fr " : " Outils de développement : " ,
" en " : " Development tools: " ,
} ,
" Disk needed: " : { " fr " : " Disque nécessaire : " , " en " : " Disk needed: " } ,
" Numbers separated by spaces, [all], blank = none: " : {
" fr " : " Numéros séparés par des espaces, [tous], vide = aucun : " ,
" en " : " Numbers separated by spaces, [all], blank = none: " ,
} ,
" No graphical VM: these tools are not installed. " : {
" fr " : " Aucune VM graphique : ces outils ne seront pas installés. " ,
" en " : " No graphical VM: these tools are not installed. " ,
} ,
" Partly skipped (arch or desktop): " : {
" fr " : " En partie écartés (architecture ou bureau) : " ,
" en " : " Partly skipped (arch or desktop): " ,
} ,
" Installing PyCharm (long) " : {
" fr " : " Installation de PyCharm (long) " ,
" en " : " Installing PyCharm (long) " ,
} ,
" no JetBrains build for " : {
" fr " : " aucune archive JetBrains pour " ,
" en " : " no JetBrains build for " ,
} ,
" PyCharm: project config skipped " : {
" fr " : " PyCharm : configuration du projet non écrite " ,
" en " : " PyCharm: project config skipped " ,
} ,
" PyCharm installed: " : {
" fr " : " PyCharm installé : " ,
" en " : " PyCharm installed: " ,
} ,
" PyCharm not installed (see above) " : {
" fr " : " PyCharm non installé (voir ci-dessus) " ,
" en " : " PyCharm not installed (see above) " ,
} ,
" Installing Android Studio (long) " : {
" fr " : " Installation d ' Android Studio (long) " ,
" en " : " Installing Android Studio (long) " ,
} ,
" Android Studio: Google publishes x86_64 only " : {
" fr " : " Android Studio : Google ne publie que du x86_64 " ,
" en " : " Android Studio: Google publishes x86_64 only " ,
} ,
2026-08-18 00:51:50 -04:00
" SDK shared through ANDROID_HOME: " : {
" fr " : " SDK partagé via ANDROID_HOME : " ,
" en " : " SDK shared through ANDROID_HOME: " ,
} ,
[ADD] todo qemu: offer PyCharm, Android Studio and GNOME extensions
A graphical VM was a desktop and nothing else: every developer tool had to
be installed by hand afterwards. A check list now carries them, filtered
per machine — Android Studio is x86_64 only, Google publishes no Linux
aarch64 build — and their disk cost reaches the plan before any qcow2 is
created. They are installed BEFORE the clone: PyCharm writes the .idea/ of
the repository, and the install that follows is what runs
pycharm_configuration.py, through update_env_version.pycharm_update().
Its launcher is named studio, which is enough to conclude the install
failed; it now answers to android-studio too. GNOME extensions come from
the site by UUID, per running Shell: the same endpoint serves gTile v59
for GNOME 46 and v62 for 48. Checked with stubs: every tool can fail and
the install's exit code still wins, 45 tests.
--- FR ---
Une VM graphique n'était qu'un bureau : chaque outil de développement
restait à poser à la main. Une liste à cocher les porte, filtrés machine
par machine — Android Studio n'existe qu'en x86_64, Google ne publiant
aucune archive Linux aarch64 — et leur place disque atteint le plan avant
qu'un seul qcow2 ne soit créé. Ils sont posés AVANT le clone : PyCharm
écrit le .idea/ du dépôt, et c'est l'installation qui suit qui lance
pycharm_configuration.py, via update_env_version.pycharm_update().
Son lanceur s'appelle studio, ce qui suffit à conclure à un échec ; il
répond désormais aussi à android-studio. Les extensions GNOME viennent du
site par UUID, selon le Shell qui tourne : le même point d'entrée sert
gTile v59 pour GNOME 46 et v62 pour 48. Vérifié avec des leurres : chaque
outil peut échouer sans que le code de sortie de l'installation ne change,
45 tests.
Assisted-by: Claude Opus 5
2026-08-17 22:57:56 -04:00
" Android Studio installed: " : {
" fr " : " Android Studio installé : " ,
" en " : " Android Studio installed: " ,
} ,
" Android Studio not installed (see above) " : {
" fr " : " Android Studio non installé (voir ci-dessus) " ,
" en " : " Android Studio not installed (see above) " ,
} ,
" no nested KVM: the emulator will not run " : {
" fr " : " pas de KVM imbriqué : l ' émulateur ne démarrera pas " ,
" en " : " no nested KVM: the emulator will not run " ,
} ,
" Suggested GNOME extensions " : {
" fr " : " Extensions GNOME suggérées " ,
" en " : " Suggested GNOME extensions " ,
} ,
" not in the repos, skipped: " : {
" fr " : " absent des dépôts, ignoré : " ,
" en " : " not in the repos, skipped: " ,
} ,
" Enable them from Extension Manager, or: " : {
" fr " : " À activer depuis le gestionnaire d ' extensions, ou : " ,
" en " : " Enable them from Extension Manager, or: " ,
} ,
" GNOME Shell not found, site extensions skipped " : {
" fr " : " GNOME Shell introuvable, extensions du site ignorées " ,
" en " : " GNOME Shell not found, site extensions skipped " ,
} ,
" installed and enabled: " : {
" fr " : " installée et activée : " ,
" en " : " installed and enabled: " ,
} ,
" not available for this GNOME, skipped: " : {
" fr " : " indisponible pour ce GNOME, ignorée : " ,
" en " : " not available for this GNOME, skipped: " ,
} ,
" log out and back in to load them " : {
" fr " : " se déconnecter puis se reconnecter pour les charger " ,
" en " : " log out and back in to load them " ,
} ,
" open the project once and close PyCharm; the .idea it writes is what the install configures " : {
" fr " : " ouvrir le projet une fois puis fermer PyCharm : le .idea qu ' il écrit est ce que l ' installation configure " ,
" en " : " open the project once and close PyCharm; the .idea it writes is what the install configures " ,
} ,
" command " : { " fr " : " commande " , " en " : " command " } ,
[FIX] todo qemu: open the PyCharm project without a screen, and unlicensed
Two measurements, one after the other. The unified PyCharm that
code=PCC&latest now serves stops on its licence: the log says
NoValidIdeLicense then "Get licenses: request requires authentication", and
no project is ever opened — so no .idea, so nothing for the install to
configure. The Community line asks for no account and is still patched
(2025.2.6.2 on 2026-07-29); it is resolved from the release feed, so no
version is frozen here.
That build then froze 1.3 s after startup: the trust dialog, invisible under
Xvfb and waiting for a click. idea.trust.all.projects unblocks it. Checked
on an Ubuntu 26.04 VM: .idea complete in 195 s, and pycharm_configuration.py
writes its exclusions into erplibre.iml.
--- FR ---
Deux mesures, l'une après l'autre. Le PyCharm unifié que sert désormais
code=PCC&latest s'arrête sur sa licence : le journal dit NoValidIdeLicense
puis « Get licenses: request requires authentication », et aucun projet ne
s'ouvre — donc pas de .idea, donc rien à configurer pour l'installation. La
ligne Community ne demande aucun compte et reste corrigée (2025.2.6.2 le
2026-07-29) ; elle est résolue depuis le flux des versions, sans qu'aucun
numéro ne soit figé ici.
Ce build se figeait ensuite 1,3 s après le démarrage : la fenêtre de
confiance, invisible sous Xvfb et attendant un clic.
idea.trust.all.projects la lève. Vérifié sur une VM Ubuntu 26.04 : .idea
complet en 195 s, et pycharm_configuration.py écrit ses exclusions dans
erplibre.iml.
Assisted-by: Claude Opus 5
2026-08-17 23:22:38 -04:00
" release feed unreachable: unified build, it will ask for a JetBrains account " : {
" fr " : " flux des versions injoignable : build unifié, il demandera un compte JetBrains " ,
" en " : " release feed unreachable: unified build, it will ask for a JetBrains account " ,
} ,
" Creating the PyCharm project (first open) " : {
" fr " : " Création du projet PyCharm (première ouverture) " ,
" en " : " Creating the PyCharm project (first open) " ,
} ,
" PyCharm missing, step skipped " : {
" fr " : " PyCharm absent, étape sautée " ,
" en " : " PyCharm missing, step skipped " ,
} ,
" no Xvfb here, open PyCharm by hand " : {
" fr " : " pas de Xvfb ici : ouvrir PyCharm à la main " ,
" en " : " no Xvfb here, open PyCharm by hand " ,
} ,
[ADD] todo qemu: build and test the mobile app, and fail the VM if it breaks
A VM was declared ready without anything proving it. The mobile option now
adds the repository to the manifest (additive, so it rides along an Odoo 18
install), runs the mobile repository's own install-android.sh — licences
accepted included — then npm ci, vite build, cap sync, gradlew assembleDebug
and npm test. A failure fails the VM: the exit code reaches the dashboard.
Two gaps in that upstream installer had to be filled: unzip and wget, which
no cloud image ships, and the SDK platform — it installs android-34 while
variables.gradle asks for compileSdk 36, so the number is read from the file
rather than frozen. Gradle writes tens of megabytes and hundreds of harmless
lines carrying the word error: that output goes to a file of its own, and the
log gets the named cause instead.
--- FR ---
Une VM était déclarée prête sans que rien ne le prouve. L'option mobile ajoute
maintenant le dépôt au manifeste (additif, donc il accompagne une installation
Odoo 18), lance l'install-android.sh du dépôt mobile lui-même — licences
acceptées comprises —, puis npm ci, vite build, cap sync, gradlew
assembleDebug et npm test. Un échec fait échouer la VM : le code de sortie
remonte au tableau de bord.
Deux manques de cet installateur amont ont dû être comblés : unzip et wget,
qu'aucune image cloud ne livre, et la plateforme SDK — il pose android-34
quand variables.gradle réclame compileSdk 36, d'où le chiffre lu dans le
fichier plutôt que figé. Gradle écrit des dizaines de mégaoctets et des
centaines de lignes anodines portant le mot error : cette sortie part dans un
fichier à part, et le journal reçoit la cause nommée.
Assisted-by: Claude Opus 5
2026-08-18 00:50:08 -04:00
" ERPLibre mobile (build) " : {
" fr " : " ERPLibre mobile (compilation) " ,
" en " : " ERPLibre mobile (build) " ,
} ,
" APK debug + Vitest, validates the VM " : {
" fr " : " APK debug + Vitest, valide la VM " ,
" en " : " APK debug + Vitest, validates the VM " ,
} ,
" Building ERPLibre mobile (long) " : {
" fr " : " Compilation d ' ERPLibre mobile (long) " ,
" en " : " Building ERPLibre mobile (long) " ,
} ,
" detailed log in the VM: " : {
" fr " : " journal détaillé dans la VM : " ,
" en " : " detailed log in the VM: " ,
} ,
" FAILED: " : { " fr " : " ÉCHEC : " , " en " : " FAILED: " } ,
" probable cause: " : { " fr " : " cause probable : " , " en " : " probable cause: " } ,
" no known pattern, last lines: " : {
" fr " : " aucun motif connu, dernières lignes : " ,
" en " : " no known pattern, last lines: " ,
} ,
" a failed mobile build marks the VM as failed " : {
" fr " : " une compilation mobile en échec marque la VM en échec " ,
" en " : " a failed mobile build marks the VM as failed " ,
} ,
[ADD] todo qemu: add an Android emulator, and fix what the real run exposed
Running it on a VM was the only way to find these. install_os never installed
python3-venv, so .venv.erplibre was born crippled — bin/python but no pip, no
activate — and everything downstream failed on "No module named git"; one line
of dependency fixes it. Capacitor 8 needs a JDK 21 where the mobile
repository's installer puts 17, and Gradle must RUN on it. That installer is
not idempotent either, so it is replayed only when something is missing.
The new emulator option creates an AVD from the SDK's own device list — the
newest plain Pixel, smallest screen — with software rendering written into its
config so ssh -X does not open a black screen, and adds the user to the kvm
group, without which it refuses to start. Checked on a VM: boot completed in
10 s, adb sees emulator-5554, Android 16 x86_64, no KVM refusal. Vitest: 1938
tests pass. The APK still fails, upstream: sentencepiece builds protoc for the
target then runs it on the host.
--- FR ---
Seule l'exécution sur une VM pouvait trouver ceci. install_os n'installait pas
python3-venv, si bien que .venv.erplibre naissait infirme — bin/python mais ni
pip ni activate — et tout ce qui en dépend tombait sur « No module named
git » ; une ligne de dépendance suffit. Capacitor 8 réclame un JDK 21 là où
l'installateur du dépôt mobile pose un 17, et Gradle doit TOURNER dessus. Cet
installateur n'est pas idempotent non plus : il n'est rejoué que s'il manque
quelque chose.
La nouvelle option crée un AVD depuis la liste de profils du SDK — le Pixel
simple le plus récent, plus petit écran —, écrit le rendu logiciel dans sa
configuration pour qu'ssh -X n'ouvre pas un écran noir, et ajoute
l'utilisateur au groupe kvm, sans quoi il refuse de démarrer. Vérifié sur une
VM : boot en 10 s, adb voit emulator-5554, Android 16 x86_64, aucun refus de
KVM. Vitest : 1938 tests passent. L'APK échoue encore, en amont :
sentencepiece bâtit protoc pour la cible puis l'exécute sur l'hôte.
Assisted-by: Claude Opus 5
2026-08-18 02:17:08 -04:00
" ERPLibre venv (everything below needs it) " : {
" fr " : " venv ERPLibre (tout ce qui suit en dépend) " ,
" en " : " ERPLibre venv (everything below needs it) " ,
} ,
[ADD] todo qemu: build and test the mobile app, and fail the VM if it breaks
A VM was declared ready without anything proving it. The mobile option now
adds the repository to the manifest (additive, so it rides along an Odoo 18
install), runs the mobile repository's own install-android.sh — licences
accepted included — then npm ci, vite build, cap sync, gradlew assembleDebug
and npm test. A failure fails the VM: the exit code reaches the dashboard.
Two gaps in that upstream installer had to be filled: unzip and wget, which
no cloud image ships, and the SDK platform — it installs android-34 while
variables.gradle asks for compileSdk 36, so the number is read from the file
rather than frozen. Gradle writes tens of megabytes and hundreds of harmless
lines carrying the word error: that output goes to a file of its own, and the
log gets the named cause instead.
--- FR ---
Une VM était déclarée prête sans que rien ne le prouve. L'option mobile ajoute
maintenant le dépôt au manifeste (additif, donc il accompagne une installation
Odoo 18), lance l'install-android.sh du dépôt mobile lui-même — licences
acceptées comprises —, puis npm ci, vite build, cap sync, gradlew
assembleDebug et npm test. Un échec fait échouer la VM : le code de sortie
remonte au tableau de bord.
Deux manques de cet installateur amont ont dû être comblés : unzip et wget,
qu'aucune image cloud ne livre, et la plateforme SDK — il pose android-34
quand variables.gradle réclame compileSdk 36, d'où le chiffre lu dans le
fichier plutôt que figé. Gradle écrit des dizaines de mégaoctets et des
centaines de lignes anodines portant le mot error : cette sortie part dans un
fichier à part, et le journal reçoit la cause nommée.
Assisted-by: Claude Opus 5
2026-08-18 00:50:08 -04:00
" mobile repository (additive manifest) " : {
" fr " : " dépôt mobile (manifeste additif) " ,
" en " : " mobile repository (additive manifest) " ,
} ,
" prerequisites of the upstream installer " : {
" fr " : " prérequis de l ' installateur amont " ,
" en " : " prerequisites of the upstream installer " ,
} ,
" Android SDK, licences, NDK " : {
" fr " : " SDK Android, licences, NDK " ,
" en " : " Android SDK, licences, NDK " ,
} ,
" SDK platform required by the project " : {
" fr " : " plateforme SDK réclamée par le projet " ,
" en " : " SDK platform required by the project " ,
} ,
" npm dependencies " : {
" fr " : " dépendances npm " ,
" en " : " npm dependencies " ,
} ,
" web bundle (vite build) " : {
" fr " : " bundle web (vite build) " ,
" en " : " web bundle (vite build) " ,
} ,
" native sync (capacitor) " : {
" fr " : " synchronisation native (capacitor) " ,
" en " : " native sync (capacitor) " ,
} ,
" debug APK (gradle) " : {
" fr " : " APK debug (gradle) " ,
" en " : " debug APK (gradle) " ,
} ,
" Vitest tests " : { " fr " : " tests Vitest " , " en " : " Vitest tests " } ,
" APK built: " : { " fr " : " APK construit : " , " en " : " APK built: " } ,
" browser debugging (no Android): " : {
" fr " : " déverminage au navigateur (sans Android) : " ,
" en " : " browser debugging (no Android): " ,
} ,
" no APK produced " : {
" fr " : " aucun APK produit " ,
" en " : " no APK produced " ,
} ,
" disk full " : { " fr " : " disque plein " , " en " : " disk full " } ,
" SDK platform missing " : {
" fr " : " plateforme SDK absente " ,
" en " : " SDK platform missing " ,
} ,
" SDK not found (ANDROID_HOME) " : {
" fr " : " SDK introuvable (ANDROID_HOME) " ,
" en " : " SDK not found (ANDROID_HOME) " ,
} ,
" SDK licences not accepted " : {
" fr " : " licences SDK non acceptées " ,
" en " : " SDK licences not accepted " ,
} ,
" NDK missing " : { " fr " : " NDK absent " , " en " : " NDK missing " } ,
[ADD] todo qemu: add an Android emulator, and fix what the real run exposed
Running it on a VM was the only way to find these. install_os never installed
python3-venv, so .venv.erplibre was born crippled — bin/python but no pip, no
activate — and everything downstream failed on "No module named git"; one line
of dependency fixes it. Capacitor 8 needs a JDK 21 where the mobile
repository's installer puts 17, and Gradle must RUN on it. That installer is
not idempotent either, so it is replayed only when something is missing.
The new emulator option creates an AVD from the SDK's own device list — the
newest plain Pixel, smallest screen — with software rendering written into its
config so ssh -X does not open a black screen, and adds the user to the kvm
group, without which it refuses to start. Checked on a VM: boot completed in
10 s, adb sees emulator-5554, Android 16 x86_64, no KVM refusal. Vitest: 1938
tests pass. The APK still fails, upstream: sentencepiece builds protoc for the
target then runs it on the host.
--- FR ---
Seule l'exécution sur une VM pouvait trouver ceci. install_os n'installait pas
python3-venv, si bien que .venv.erplibre naissait infirme — bin/python mais ni
pip ni activate — et tout ce qui en dépend tombait sur « No module named
git » ; une ligne de dépendance suffit. Capacitor 8 réclame un JDK 21 là où
l'installateur du dépôt mobile pose un 17, et Gradle doit TOURNER dessus. Cet
installateur n'est pas idempotent non plus : il n'est rejoué que s'il manque
quelque chose.
La nouvelle option crée un AVD depuis la liste de profils du SDK — le Pixel
simple le plus récent, plus petit écran —, écrit le rendu logiciel dans sa
configuration pour qu'ssh -X n'ouvre pas un écran noir, et ajoute
l'utilisateur au groupe kvm, sans quoi il refuse de démarrer. Vérifié sur une
VM : boot en 10 s, adb voit emulator-5554, Android 16 x86_64, aucun refus de
KVM. Vitest : 1938 tests passent. L'APK échoue encore, en amont :
sentencepiece bâtit protoc pour la cible puis l'exécute sur l'hôte.
Assisted-by: Claude Opus 5
2026-08-18 02:17:08 -04:00
" JDK required by the project missing " : {
" fr " : " JDK réclamé par le projet absent " ,
" en " : " JDK required by the project missing " ,
} ,
" Gradle running on too old a JDK " : {
" fr " : " Gradle tourne sur un JDK trop ancien " ,
" en " : " Gradle running on too old a JDK " ,
} ,
" SDK already there (upstream installer replays) " : {
" fr " : " SDK déjà là (l ' installateur amont se rejoue) " ,
" en " : " SDK already there (upstream installer replays) " ,
} ,
" cross-compiled protoc run on the host " : {
" fr " : " protoc compilé pour la cible, exécuté sur l ' hôte " ,
" en " : " cross-compiled protoc run on the host " ,
} ,
[ADD] todo qemu: build and test the mobile app, and fail the VM if it breaks
A VM was declared ready without anything proving it. The mobile option now
adds the repository to the manifest (additive, so it rides along an Odoo 18
install), runs the mobile repository's own install-android.sh — licences
accepted included — then npm ci, vite build, cap sync, gradlew assembleDebug
and npm test. A failure fails the VM: the exit code reaches the dashboard.
Two gaps in that upstream installer had to be filled: unzip and wget, which
no cloud image ships, and the SDK platform — it installs android-34 while
variables.gradle asks for compileSdk 36, so the number is read from the file
rather than frozen. Gradle writes tens of megabytes and hundreds of harmless
lines carrying the word error: that output goes to a file of its own, and the
log gets the named cause instead.
--- FR ---
Une VM était déclarée prête sans que rien ne le prouve. L'option mobile ajoute
maintenant le dépôt au manifeste (additif, donc il accompagne une installation
Odoo 18), lance l'install-android.sh du dépôt mobile lui-même — licences
acceptées comprises —, puis npm ci, vite build, cap sync, gradlew
assembleDebug et npm test. Un échec fait échouer la VM : le code de sortie
remonte au tableau de bord.
Deux manques de cet installateur amont ont dû être comblés : unzip et wget,
qu'aucune image cloud ne livre, et la plateforme SDK — il pose android-34
quand variables.gradle réclame compileSdk 36, d'où le chiffre lu dans le
fichier plutôt que figé. Gradle écrit des dizaines de mégaoctets et des
centaines de lignes anodines portant le mot error : cette sortie part dans un
fichier à part, et le journal reçoit la cause nommée.
Assisted-by: Claude Opus 5
2026-08-18 00:50:08 -04:00
" JDK/Gradle mismatch " : {
" fr " : " JDK et Gradle incompatibles " ,
" en " : " JDK/Gradle mismatch " ,
} ,
" Gradle dependency unreachable (network?) " : {
" fr " : " dépendance Gradle injoignable (réseau ?) " ,
" en " : " Gradle dependency unreachable (network?) " ,
} ,
" Vitest tests failed " : {
" fr " : " tests Vitest en échec " ,
" en " : " Vitest tests failed " ,
} ,
[ADD] todo qemu: add an Android emulator, and fix what the real run exposed
Running it on a VM was the only way to find these. install_os never installed
python3-venv, so .venv.erplibre was born crippled — bin/python but no pip, no
activate — and everything downstream failed on "No module named git"; one line
of dependency fixes it. Capacitor 8 needs a JDK 21 where the mobile
repository's installer puts 17, and Gradle must RUN on it. That installer is
not idempotent either, so it is replayed only when something is missing.
The new emulator option creates an AVD from the SDK's own device list — the
newest plain Pixel, smallest screen — with software rendering written into its
config so ssh -X does not open a black screen, and adds the user to the kvm
group, without which it refuses to start. Checked on a VM: boot completed in
10 s, adb sees emulator-5554, Android 16 x86_64, no KVM refusal. Vitest: 1938
tests pass. The APK still fails, upstream: sentencepiece builds protoc for the
target then runs it on the host.
--- FR ---
Seule l'exécution sur une VM pouvait trouver ceci. install_os n'installait pas
python3-venv, si bien que .venv.erplibre naissait infirme — bin/python mais ni
pip ni activate — et tout ce qui en dépend tombait sur « No module named
git » ; une ligne de dépendance suffit. Capacitor 8 réclame un JDK 21 là où
l'installateur du dépôt mobile pose un 17, et Gradle doit TOURNER dessus. Cet
installateur n'est pas idempotent non plus : il n'est rejoué que s'il manque
quelque chose.
La nouvelle option crée un AVD depuis la liste de profils du SDK — le Pixel
simple le plus récent, plus petit écran —, écrit le rendu logiciel dans sa
configuration pour qu'ssh -X n'ouvre pas un écran noir, et ajoute
l'utilisateur au groupe kvm, sans quoi il refuse de démarrer. Vérifié sur une
VM : boot en 10 s, adb voit emulator-5554, Android 16 x86_64, aucun refus de
KVM. Vitest : 1938 tests passent. L'APK échoue encore, en amont :
sentencepiece bâtit protoc pour la cible puis l'exécute sur l'hôte.
Assisted-by: Claude Opus 5
2026-08-18 02:17:08 -04:00
" ERPLibre venv incomplete (no pip: python3-venv) " : {
" fr " : " venv ERPLibre incomplet (pas de pip : python3-venv) " ,
" en " : " ERPLibre venv incomplete (no pip: python3-venv) " ,
} ,
" mobile repository missing " : {
" fr " : " dépôt mobile absent " ,
" en " : " mobile repository missing " ,
} ,
" ERPLibre venv missing (incomplete install) " : {
" fr " : " venv ERPLibre absent (installation incomplète) " ,
" en " : " ERPLibre venv missing (incomplete install) " ,
} ,
[ADD] todo qemu: offer the adb tunnel for scrcpy, X11-free
X11 carries raw pixels: 0.62 megapixel per frame even after shrinking the
screen, rendered in software. scrcpy receives an H.264 stream encoded BY the
device, so the emulator runs with no window at all — no X11 anywhere. The
tunnel menu gained a fourth kind, reusing the target list it already reads from
~/.ssh/config, ProxyJump included: the hard part was already solved there.
It forwards 5555, the emulator's own adb port, not 5037: tunnelling the adb
server would force the user to kill the one on their workstation, which holds
the same port. Verified through the tunnel from the hypervisor: an adb CNXN
handshake answers device::ro.product.name=sdk_gphone64_x86, exactly what adb
connect does. ss -ltn in the VM confirms 5554, 5555 and 5037 all listen on
127.0.0.1.
--- FR ---
X11 transporte des pixels bruts : 0,62 mégapixel par image même après réduction
de l'écran, en rendu logiciel. scrcpy reçoit un flux H.264 encodé PAR
l'appareil, si bien que l'émulateur tourne sans aucune fenêtre — plus de X11
nulle part. Le menu de tunnel gagne un quatrième type, qui réutilise la liste de
cibles qu'il lit déjà dans ~/.ssh/config, ProxyJump compris : le plus dur y
était déjà résolu.
Il redirige 5555, le port adb de l'émulateur lui-même, et non 5037 : tunneliser
le serveur adb obligerait à tuer celui du poste, qui occupe le même port.
Vérifié à travers le tunnel depuis l'hyperviseur : une poignée de main adb CNXN
répond device::ro.product.name=sdk_gphone64_x86, ce que fait exactement adb
connect. « ss -ltn » dans la VM confirme que 5554, 5555 et 5037 écoutent tous
sur 127.0.0.1.
Assisted-by: Claude Opus 5
2026-08-19 02:39:10 -04:00
" smoother, without X11: " : {
" fr " : " plus fluide, sans X11 : " ,
" en " : " smoother, without X11: " ,
} ,
[ADD] script todo: lancer l'émulateur Android et son tunnel adb
Le tunnel n'était que du texte à recopier, et le démarrage restait manuel :
un second émulateur sur le même AVD tue le premier (« Running multiple
emulators »), rencontré deux fois. Le menu le détecte donc avant de lancer.
La fenêtre décide du reste. Sans elle, todo.py démarre à distance, détaché
par setsid dans le groupe kvm, et enchaîne sur scrcpy ; avec elle, la
commande doit partir du poste qui possède l'affichage, pas d'ici.
Mesuré : l'émulateur n'écoute que sur le 127.0.0.1 de la VM — l'hyperviseur
est refusé sur IP:5555 — d'où le -J qui met la VM en dernier saut. Vérifié
sur erplibre-mobile-proof : poignée de main adb CNXN rendant
sdk_gphone64_x86_64, enveloppe détachée survivant au ssh ; 30 tests.
--- EN ---
The tunnel was only text to copy, and starting the emulator stayed manual:
a second emulator on the same AVD kills the first ("Running multiple
emulators"), hit twice. The menu now detects it before starting.
The window decides the rest. Without one, todo.py starts it remotely,
detached by setsid in the kvm group, and chains into scrcpy; with one, the
command must run from the workstation that owns the display, not from here.
Measured: the emulator only listens on the VM's 127.0.0.1 — the hypervisor
is refused on IP:5555 — hence the -J putting the VM last. Verified on
erplibre-mobile-proof: an adb CNXN handshake returning sdk_gphone64_x86_64,
a detached wrapper outliving the ssh; 30 tests.
Assisted-by: Claude Opus 5
2026-08-19 03:35:31 -04:00
" Android emulator " : { " fr " : " Émulateur Android " , " en " : " Android emulator " } ,
" Android emulator (start, tunnel, scrcpy) " : {
" fr " : " Émulateur Android (démarrer, tunnel, scrcpy) " ,
" en " : " Android emulator (start, tunnel, scrcpy) " ,
} ,
" An emulator is already running on this VM. " : {
" fr " : " Un émulateur tourne déjà sur cette VM. " ,
" en " : " An emulator is already running on this VM. " ,
} ,
" Only one per AVD; close it first: " : {
" fr " : " Un seul par AVD ; le fermer d ' abord : " ,
" en " : " Only one per AVD; close it first: " ,
} ,
" Close it now? (y/N): " : {
" fr " : " Le fermer maintenant ? (o/N) : " ,
" en " : " Close it now? (y/N): " ,
} ,
" Closed. " : { " fr " : " Fermé. " , " en " : " Closed. " } ,
" Show a window? " : {
" fr " : " Afficher une fenêtre ? " ,
" en " : " Show a window? " ,
} ,
" No window - stream with scrcpy (smoother) " : {
" fr " : " Sans fenêtre — flux par scrcpy (plus fluide) " ,
" en " : " No window - stream with scrcpy (smoother) " ,
} ,
" Window over ssh -X (raw pixels, slower) " : {
" fr " : " Fenêtre par ssh -X (pixels bruts, plus lent) " ,
" en " : " Window over ssh -X (raw pixels, slower) " ,
} ,
" X11 compression is on (-XC); the screen is 540x1140. " : {
" fr " : " La compression X11 est active (-XC) ; l ' écran fait 540x1140. " ,
" en " : " X11 compression is on (-XC); the screen is 540x1140. " ,
} ,
" Starting the emulator without a window... " : {
" fr " : " Démarrage de l ' émulateur sans fenêtre… " ,
" en " : " Starting the emulator without a window... " ,
} ,
" Could not start it: " : {
" fr " : " Démarrage impossible : " ,
" en " : " Could not start it: " ,
} ,
" Started. Boot takes about a minute; log in the VM: " : {
" fr " : " Démarré. L ' amorçage prend environ une minute ; journal dans la VM : " ,
" en " : " Started. Boot takes about a minute; log in the VM: " ,
} ,
" 1. Emulator started, without a window. " : {
" fr " : " 1. Émulateur démarré, sans fenêtre. " ,
" en " : " 1. Emulator started, without a window. " ,
} ,
" If scrcpy will run on THIS machine, I can open it. " : {
" fr " : " Si scrcpy tournera sur CETTE machine, je peux l ' ouvrir. " ,
" en " : " If scrcpy will run on THIS machine, I can open it. " ,
} ,
" Open the tunnel now? (y/N): " : {
" fr " : " Ouvrir le tunnel maintenant ? (o/N) : " ,
" en " : " Open the tunnel now? (y/N): " ,
} ,
" Port already in use here: " : {
" fr " : " Port déjà occupé ici : " ,
" en " : " Port already in use here: " ,
} ,
" Close the other tunnel first: " : {
" fr " : " Fermer l ' autre tunnel d ' abord : " ,
" en " : " Close the other tunnel first: " ,
} ,
" Tunnel failed: " : { " fr " : " Tunnel en échec : " , " en " : " Tunnel failed: " } ,
" Tunnel open on localhost: " : {
" fr " : " Tunnel ouvert sur localhost: " ,
" en " : " Tunnel open on localhost: " ,
} ,
" Then: " : { " fr " : " Puis : " , " en " : " Then: " } ,
" To close it: " : { " fr " : " Pour le fermer : " , " en " : " To close it: " } ,
[ADD] todo qemu: offer the adb tunnel for scrcpy, X11-free
X11 carries raw pixels: 0.62 megapixel per frame even after shrinking the
screen, rendered in software. scrcpy receives an H.264 stream encoded BY the
device, so the emulator runs with no window at all — no X11 anywhere. The
tunnel menu gained a fourth kind, reusing the target list it already reads from
~/.ssh/config, ProxyJump included: the hard part was already solved there.
It forwards 5555, the emulator's own adb port, not 5037: tunnelling the adb
server would force the user to kill the one on their workstation, which holds
the same port. Verified through the tunnel from the hypervisor: an adb CNXN
handshake answers device::ro.product.name=sdk_gphone64_x86, exactly what adb
connect does. ss -ltn in the VM confirms 5554, 5555 and 5037 all listen on
127.0.0.1.
--- FR ---
X11 transporte des pixels bruts : 0,62 mégapixel par image même après réduction
de l'écran, en rendu logiciel. scrcpy reçoit un flux H.264 encodé PAR
l'appareil, si bien que l'émulateur tourne sans aucune fenêtre — plus de X11
nulle part. Le menu de tunnel gagne un quatrième type, qui réutilise la liste de
cibles qu'il lit déjà dans ~/.ssh/config, ProxyJump compris : le plus dur y
était déjà résolu.
Il redirige 5555, le port adb de l'émulateur lui-même, et non 5037 : tunneliser
le serveur adb obligerait à tuer celui du poste, qui occupe le même port.
Vérifié à travers le tunnel depuis l'hyperviseur : une poignée de main adb CNXN
répond device::ro.product.name=sdk_gphone64_x86, ce que fait exactement adb
connect. « ss -ltn » dans la VM confirme que 5554, 5555 et 5037 écoutent tous
sur 127.0.0.1.
Assisted-by: Claude Opus 5
2026-08-19 02:39:10 -04:00
" Android emulator (adb 5555, then scrcpy) " : {
" fr " : " Émulateur Android (adb 5555, puis scrcpy) " ,
" en " : " Android emulator (adb 5555, then scrcpy) " ,
} ,
" Android emulator over adb + scrcpy " : {
" fr " : " Émulateur Android par adb + scrcpy " ,
" en " : " Android emulator over adb + scrcpy " ,
} ,
" 1. In the VM, start the emulator WITHOUT a window: " : {
" fr " : " 1. Dans la VM, démarrer l ' émulateur SANS fenêtre : " ,
" en " : " 1. In the VM, start the emulator WITHOUT a window: " ,
} ,
" 2. Open the tunnel from YOUR workstation: " : {
" fr " : " 2. Ouvrir le tunnel depuis VOTRE poste : " ,
" en " : " 2. Open the tunnel from YOUR workstation: " ,
} ,
" 3. Then, still on your workstation: " : {
" fr " : " 3. Puis, toujours sur votre poste : " ,
" en " : " 3. Then, still on your workstation: " ,
} ,
" scrcpy on Debian/Ubuntu: " : {
" fr " : " scrcpy sur Debian/Ubuntu : " ,
" en " : " scrcpy on Debian/Ubuntu: " ,
} ,
[ADD] todo qemu: add an Android emulator, and fix what the real run exposed
Running it on a VM was the only way to find these. install_os never installed
python3-venv, so .venv.erplibre was born crippled — bin/python but no pip, no
activate — and everything downstream failed on "No module named git"; one line
of dependency fixes it. Capacitor 8 needs a JDK 21 where the mobile
repository's installer puts 17, and Gradle must RUN on it. That installer is
not idempotent either, so it is replayed only when something is missing.
The new emulator option creates an AVD from the SDK's own device list — the
newest plain Pixel, smallest screen — with software rendering written into its
config so ssh -X does not open a black screen, and adds the user to the kvm
group, without which it refuses to start. Checked on a VM: boot completed in
10 s, adb sees emulator-5554, Android 16 x86_64, no KVM refusal. Vitest: 1938
tests pass. The APK still fails, upstream: sentencepiece builds protoc for the
target then runs it on the host.
--- FR ---
Seule l'exécution sur une VM pouvait trouver ceci. install_os n'installait pas
python3-venv, si bien que .venv.erplibre naissait infirme — bin/python mais ni
pip ni activate — et tout ce qui en dépend tombait sur « No module named
git » ; une ligne de dépendance suffit. Capacitor 8 réclame un JDK 21 là où
l'installateur du dépôt mobile pose un 17, et Gradle doit TOURNER dessus. Cet
installateur n'est pas idempotent non plus : il n'est rejoué que s'il manque
quelque chose.
La nouvelle option crée un AVD depuis la liste de profils du SDK — le Pixel
simple le plus récent, plus petit écran —, écrit le rendu logiciel dans sa
configuration pour qu'ssh -X n'ouvre pas un écran noir, et ajoute
l'utilisateur au groupe kvm, sans quoi il refuse de démarrer. Vérifié sur une
VM : boot en 10 s, adb voit emulator-5554, Android 16 x86_64, aucun refus de
KVM. Vitest : 1938 tests passent. L'APK échoue encore, en amont :
sentencepiece bâtit protoc pour la cible puis l'exécute sur l'hôte.
Assisted-by: Claude Opus 5
2026-08-18 02:17:08 -04:00
" Android emulator (Pixel) " : {
" fr " : " Émulateur Android (Pixel) " ,
" en " : " Android emulator (Pixel) " ,
} ,
" AVD viewable over ssh -X " : {
" fr " : " AVD visible par ssh -X " ,
" en " : " AVD viewable over ssh -X " ,
} ,
" Android emulator (AVD) " : {
" fr " : " Émulateur Android (AVD) " ,
" en " : " Android emulator (AVD) " ,
} ,
" no /dev/kvm: nested virtualisation is off on the host " : {
" fr " : " pas de /dev/kvm : virtualisation imbriquée coupée sur l ' hôte " ,
" en " : " no /dev/kvm: nested virtualisation is off on the host " ,
} ,
" user added to the kvm group (effective at next login) " : {
" fr " : " utilisateur ajouté au groupe kvm (effectif à la prochaine session) " ,
" en " : " user added to the kvm group (effective at next login) " ,
} ,
" emulator and system image " : {
" fr " : " émulateur et image système " ,
" en " : " emulator and system image " ,
} ,
" Pixel profile, smallest screen " : {
" fr " : " profil Pixel, plus petit écran " ,
" en " : " Pixel profile, smallest screen " ,
} ,
" create the AVD " : { " fr " : " création de l ' AVD " , " en " : " create the AVD " } ,
" AVD ready: " : { " fr " : " AVD prêt : " , " en " : " AVD ready: " } ,
" open it from your workstation: " : {
" fr " : " à ouvrir depuis votre poste : " ,
" en " : " open it from your workstation: " ,
} ,
" then install the APK: " : {
" fr " : " puis installer l ' APK : " ,
" en " : " then install the APK: " ,
} ,
" ERPLibre mobile, Android SDK (long) " : {
" fr " : " ERPLibre mobile, SDK Android (long) " ,
" en " : " ERPLibre mobile, Android SDK (long) " ,
} ,
[ADD] todo qemu: build and test the mobile app, and fail the VM if it breaks
A VM was declared ready without anything proving it. The mobile option now
adds the repository to the manifest (additive, so it rides along an Odoo 18
install), runs the mobile repository's own install-android.sh — licences
accepted included — then npm ci, vite build, cap sync, gradlew assembleDebug
and npm test. A failure fails the VM: the exit code reaches the dashboard.
Two gaps in that upstream installer had to be filled: unzip and wget, which
no cloud image ships, and the SDK platform — it installs android-34 while
variables.gradle asks for compileSdk 36, so the number is read from the file
rather than frozen. Gradle writes tens of megabytes and hundreds of harmless
lines carrying the word error: that output goes to a file of its own, and the
log gets the named cause instead.
--- FR ---
Une VM était déclarée prête sans que rien ne le prouve. L'option mobile ajoute
maintenant le dépôt au manifeste (additif, donc il accompagne une installation
Odoo 18), lance l'install-android.sh du dépôt mobile lui-même — licences
acceptées comprises —, puis npm ci, vite build, cap sync, gradlew
assembleDebug et npm test. Un échec fait échouer la VM : le code de sortie
remonte au tableau de bord.
Deux manques de cet installateur amont ont dû être comblés : unzip et wget,
qu'aucune image cloud ne livre, et la plateforme SDK — il pose android-34
quand variables.gradle réclame compileSdk 36, d'où le chiffre lu dans le
fichier plutôt que figé. Gradle écrit des dizaines de mégaoctets et des
centaines de lignes anodines portant le mot error : cette sortie part dans un
fichier à part, et le journal reçoit la cause nommée.
Assisted-by: Claude Opus 5
2026-08-18 00:50:08 -04:00
" Gradle task failed " : {
" fr " : " tâche Gradle en échec " ,
" en " : " Gradle task failed " ,
} ,
[FIX] todo qemu: open the PyCharm project without a screen, and unlicensed
Two measurements, one after the other. The unified PyCharm that
code=PCC&latest now serves stops on its licence: the log says
NoValidIdeLicense then "Get licenses: request requires authentication", and
no project is ever opened — so no .idea, so nothing for the install to
configure. The Community line asks for no account and is still patched
(2025.2.6.2 on 2026-07-29); it is resolved from the release feed, so no
version is frozen here.
That build then froze 1.3 s after startup: the trust dialog, invisible under
Xvfb and waiting for a click. idea.trust.all.projects unblocks it. Checked
on an Ubuntu 26.04 VM: .idea complete in 195 s, and pycharm_configuration.py
writes its exclusions into erplibre.iml.
--- FR ---
Deux mesures, l'une après l'autre. Le PyCharm unifié que sert désormais
code=PCC&latest s'arrête sur sa licence : le journal dit NoValidIdeLicense
puis « Get licenses: request requires authentication », et aucun projet ne
s'ouvre — donc pas de .idea, donc rien à configurer pour l'installation. La
ligne Community ne demande aucun compte et reste corrigée (2025.2.6.2 le
2026-07-29) ; elle est résolue depuis le flux des versions, sans qu'aucun
numéro ne soit figé ici.
Ce build se figeait ensuite 1,3 s après le démarrage : la fenêtre de
confiance, invisible sous Xvfb et attendant un clic.
idea.trust.all.projects la lève. Vérifié sur une VM Ubuntu 26.04 : .idea
complet en 195 s, et pycharm_configuration.py écrit ses exclusions dans
erplibre.iml.
Assisted-by: Claude Opus 5
2026-08-17 23:22:38 -04:00
" project created, the install will configure it " : {
" fr " : " projet créé, l ' installation va le configurer " ,
" en " : " project created, the install will configure it " ,
} ,
" no .idea: open PyCharm once, then " : {
" fr " : " pas de .idea : ouvrir PyCharm une fois, puis " ,
" en " : " no .idea: open PyCharm once, then " ,
} ,
[ADD] todo qemu: offer PyCharm, Android Studio and GNOME extensions
A graphical VM was a desktop and nothing else: every developer tool had to
be installed by hand afterwards. A check list now carries them, filtered
per machine — Android Studio is x86_64 only, Google publishes no Linux
aarch64 build — and their disk cost reaches the plan before any qcow2 is
created. They are installed BEFORE the clone: PyCharm writes the .idea/ of
the repository, and the install that follows is what runs
pycharm_configuration.py, through update_env_version.pycharm_update().
Its launcher is named studio, which is enough to conclude the install
failed; it now answers to android-studio too. GNOME extensions come from
the site by UUID, per running Shell: the same endpoint serves gTile v59
for GNOME 46 and v62 for 48. Checked with stubs: every tool can fail and
the install's exit code still wins, 45 tests.
--- FR ---
Une VM graphique n'était qu'un bureau : chaque outil de développement
restait à poser à la main. Une liste à cocher les porte, filtrés machine
par machine — Android Studio n'existe qu'en x86_64, Google ne publiant
aucune archive Linux aarch64 — et leur place disque atteint le plan avant
qu'un seul qcow2 ne soit créé. Ils sont posés AVANT le clone : PyCharm
écrit le .idea/ du dépôt, et c'est l'installation qui suit qui lance
pycharm_configuration.py, via update_env_version.pycharm_update().
Son lanceur s'appelle studio, ce qui suffit à conclure à un échec ; il
répond désormais aussi à android-studio. Les extensions GNOME viennent du
site par UUID, selon le Shell qui tourne : le même point d'entrée sert
gTile v59 pour GNOME 46 et v62 pour 48. Vérifié avec des leurres : chaque
outil peut échouer sans que le code de sortie de l'installation ne change,
45 tests.
Assisted-by: Claude Opus 5
2026-08-17 22:57:56 -04:00
" project " : { " fr " : " projet " , " en " : " project " } ,
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
" Instance " : {
" fr " : " 🛰 Instance " ,
" en " : " 🛰 Instance " ,
} ,
" Monitoring - a backup, a remote copy or a live instance " : {
" fr " : " 🛰 Monitoring - une sauvegarde, une copie distante ou une instance vivante " ,
" en " : " 🛰 Monitoring - a backup, a remote copy or a live instance " ,
} ,
" Inspect an instance. " : {
" fr " : " Ausculter une instance. " ,
" en " : " Inspect an instance. " ,
} ,
" A local backup .zip " : {
" fr " : " Une sauvegarde .zip locale " ,
" en " : " A local backup .zip " ,
} ,
" A remote backup (https + master password) " : {
" fr " : " Une sauvegarde distante (https + mot de passe maître) " ,
" en " : " A remote backup (https + master password) " ,
} ,
" A live remote instance " : {
" fr " : " Une instance vivante distante " ,
" en " : " A live remote instance " ,
} ,
" An API key " : {
" fr " : " Une clé d ' API " ,
" en " : " An API key " ,
} ,
" A password " : {
" fr " : " Un mot de passe " ,
" en " : " A password " ,
} ,
" API key: " : {
" fr " : " Clé d ' API : " ,
" en " : " API key: " ,
} ,
" Password: " : {
" fr " : " Mot de passe : " ,
" en " : " Password: " ,
} ,
" User login: " : {
" fr " : " Identifiant : " ,
" en " : " User login: " ,
} ,
" Instance URL (ex. https://example.com): " : {
" fr " : " URL de l ' instance (ex. https://exemple.com) : " ,
" en " : " Instance URL (ex. https://example.com): " ,
} ,
" Database name on that instance: " : {
" fr " : " Nom de la base sur cette instance : " ,
" en " : " Database name on that instance: " ,
} ,
" Cannot connect: " : {
" fr " : " Connexion impossible : " ,
" en " : " Cannot connect: " ,
} ,
" Connected as uid " : {
" fr " : " Connecté sous l ' uid " ,
" en " : " Connected as uid " ,
} ,
" The instance refused these credentials. " : {
" fr " : " L ' instance a refusé ces identifiants. " ,
" en " : " The instance refused these credentials. " ,
} ,
" Monitoring only reads; refused method: " : {
" fr " : " Le monitoring ne fait que lire ; méthode refusée : " ,
" en " : " Monitoring only reads; refused method: " ,
} ,
" Database: " : {
" fr " : " Base : " ,
" en " : " Database: " ,
} ,
" Live instance: " : {
" fr " : " Instance vivante : " ,
" en " : " Live instance: " ,
} ,
" Database name (default= " : {
" fr " : " Nom de la base (défaut= " ,
" en " : " Database name (default= " ,
} ,
" Neutralize the database (Y/n)? " : {
" fr " : " Neutraliser la base (O/n) ? " ,
" en " : " Neutralize the database (Y/n)? " ,
} ,
" The restore failed. " : {
" fr " : " La restauration a échoué. " ,
" en " : " The restore failed. " ,
} ,
" The download did not produce a usable file. " : {
" fr " : " Le téléchargement n ' a pas produit de fichier utilisable. " ,
" en " : " The download did not produce a usable file. " ,
} ,
" db_restore only reads image_db/. Link it there " : {
" fr " : " db_restore ne lit que image_db/. Y poser un lien " ,
" en " : " db_restore only reads image_db/. Link it there " ,
} ,
" Using the file already in image_db: " : {
" fr " : " On prend le fichier déjà dans image_db : " ,
" en " : " Using the file already in image_db: " ,
} ,
" Cannot link into image_db: " : {
" fr " : " Impossible de poser le lien dans image_db : " ,
" en " : " Cannot link into image_db: " ,
} ,
" You can log in with test / test. " : {
" fr " : " Vous pouvez vous connecter avec test / test. " ,
" en " : " You can log in with test / test. " ,
} ,
" update_prod_to_dev did not finish: do not " : {
" fr " : " update_prod_to_dev n ' a pas fini : ne comptez pas " ,
" en " : " update_prod_to_dev did not finish: do not " ,
} ,
" count on the test/test account. " : {
" fr " : " sur le compte test/test. " ,
" en " : " count on the test/test account. " ,
} ,
" database.is_neutralized is set. " : {
" fr " : " database.is_neutralized est posé. " ,
" en " : " database.is_neutralized is set. " ,
} ,
" database.is_neutralized is NOT set — Odoo does not " : {
" fr " : " database.is_neutralized n ' est PAS posé — Odoo ne considère pas " ,
" en " : " database.is_neutralized is NOT set — Odoo does not " ,
} ,
" consider this database neutralised. " : {
" fr " : " cette base comme neutralisée. " ,
" en " : " consider this database neutralised. " ,
} ,
" scheduled actions are still active. " : {
" fr " : " actions planifiées sont encore actives. " ,
" en " : " scheduled actions are still active. " ,
} ,
" No scheduled action is active. " : {
" fr " : " Aucune action planifiée n ' est active. " ,
" en " : " No scheduled action is active. " ,
} ,
" No mail server at all: Odoo falls back to smtp_server " : {
" fr " : " Aucun serveur de courriel : Odoo retombe sur le smtp_server " ,
" en " : " No mail server at all: Odoo falls back to smtp_server " ,
} ,
" from the config file. A blocking one is safer than none. " : {
" fr " : " du fichier de configuration. Un serveur bouchon vaut mieux que rien. " ,
" en " : " from the config file. A blocking one is safer than none. " ,
} ,
" payment provider(s) are neither disabled nor " : {
" fr " : " fournisseur(s) de paiement ne sont ni désactivés ni " ,
" en " : " payment provider(s) are neither disabled nor " ,
} ,
" in test mode. " : {
" fr " : " en mode test. " ,
" en " : " in test mode. " ,
} ,
" Run " : {
" fr " : " Lancer " ,
" en " : " Run " ,
} ,
" Enter to run it. " : {
" fr " : " Entrée pour le lancer. " ,
" en " : " Enter to run it. " ,
} ,
" Not available for this source. " : {
" fr " : " Indisponible pour cette provenance. " ,
" en " : " Not available for this source. " ,
} ,
" Migration leftovers " : {
2026-08-24 14:40:25 -04:00
" fr " : " 🚚 Restant de migration " ,
" en " : " 🚚 Migration leftovers " ,
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
} ,
" What a migration left behind, judged without needing the step-by-step databases. " : {
" fr " : " Ce qu ' une migration a laissé derrière elle, jugé sans avoir besoin des bases de palier. " ,
" en " : " What a migration left behind, judged without needing the step-by-step databases. " ,
} ,
" It reads pg_catalog — indexes and real tables — which no RPC session exposes. " : {
" fr " : " Il lit pg_catalog — les index et les vraies tables — qu ' aucune session RPC n ' expose. " ,
" en " : " It reads pg_catalog — indexes and real tables — which no RPC session exposes. " ,
} ,
" Every view someone changed, and every website copy that shadows a module view. " : {
" fr " : " Chaque vue que quelqu ' un a modifiée, et chaque copie de site web qui masque une vue de module. " ,
" en " : " Every view someone changed, and every website copy that shadows a module view. " ,
} ,
" Comparing a copy with the module view it hides is a join on arch_db, done in SQL. " : {
" fr " : " Comparer une copie à la vue de module qu ' elle masque est une jointure sur arch_db, faite en SQL. " ,
" en " : " Comparing a copy with the module view it hides is a join on arch_db, done in SQL. " ,
} ,
" Fields and models added outside any module, and which of them have no column behind them. " : {
" fr " : " Champs et modèles ajoutés hors de tout module, et lesquels n ' ont aucune colonne derrière eux. " ,
" en " : " Fields and models added outside any module, and which of them have no column behind them. " ,
} ,
" Telling a declared field from a real column means reading pg_attribute. " : {
" fr " : " Distinguer un champ déclaré d ' une vraie colonne demande de lire pg_attribute. " ,
" en " : " Telling a declared field from a real column means reading pg_attribute. " ,
} ,
" What a migration left behind, read in one database. " : {
" fr " : " Ce qu ' une migration a laissé derrière elle, lu dans une seule base. " ,
" en " : " What a migration left behind, read in one database. " ,
} ,
" Migration residue in " : {
" fr " : " Restant de migration dans " ,
" en " : " Migration residue in " ,
} ,
" base module version: " : {
" fr " : " version du module base : " ,
" en " : " base module version: " ,
} ,
" None of the checks found anything. " : {
" fr " : " Aucun contrôle n ' a rien trouvé. " ,
" en " : " None of the checks found anything. " ,
} ,
" This reads one database on its own — it cannot see " : {
" fr " : " Ceci ne lit qu ' une base isolée — il ne peut pas voir " ,
" en " : " This reads one database on its own — it cannot see " ,
} ,
" what an earlier step silently dropped. " : {
" fr " : " ce qu ' un palier antérieur a laissé tomber en silence. " ,
" en " : " what an earlier step silently dropped. " ,
} ,
" could not read " : {
" fr " : " lecture impossible " ,
" en " : " could not read " ,
} ,
" no repair tool yet " : {
" fr " : " pas encore d ' outil de réparation " ,
" en " : " no repair tool yet " ,
} ,
" Modules stuck between two states " : {
" fr " : " Modules figés entre deux états " ,
" en " : " Modules stuck between two states " ,
} ,
" A migration that stopped mid-flight leaves them there; Odoo will retry the transition at every start. " : {
" fr " : " Une migration arrêtée en vol les laisse ainsi ; Odoo retentera la transition à chaque démarrage. " ,
" en " : " A migration that stopped mid-flight leaves them there; Odoo will retry the transition at every start. " ,
} ,
" Languages whose active flag is NULL " : {
" fr " : " Langues dont le drapeau actif est NULL " ,
" en " : " Languages whose active flag is NULL " ,
} ,
" A boolean that is neither true nor false: the language is listed nowhere and cannot be re-enabled from the interface. " : {
" fr " : " Un booléen qui n ' est ni vrai ni faux : la langue n ' est listée nulle part et ne peut plus être réactivée depuis l ' interface. " ,
" en " : " A boolean that is neither true nor false: the language is listed nowhere and cannot be re-enabled from the interface. " ,
} ,
" Indexes duplicated by the Odoo 17 renaming " : {
" fr " : " Index doublés par le renommage d ' Odoo 17 " ,
" en " : " Indexes duplicated by the Odoo 17 renaming " ,
} ,
[ADD] migration: brancher les deux réparations qui ne tournaient jamais
fix_duplicate_index et restore_config_defaults étaient écrits, éprouvés,
et absents du pilote. Chaque migration refabriquait donc ses index
redondants et reperdait sa liste de prix.
Mesuré sur DEUX chaînes 12 → 18 indépendantes, même base source : 68
relations orphelines, 9 langues au drapeau NULL, une liste de prix
absente, 376 paires d'index en double — les mêmes nombres des deux côtés.
Ce n'est pas un accident d'exécution, c'est le chemin lui-même.
Les index à partir du palier 17 : avant, la convention n'a pas changé.
Les réglages au DERNIER palier : l'outil charge le registre, et seul
l'état final compte. Les deux en wait_at_error=False — avec --apply, le
code 1 dit « il en reste », pas « je suis tombé », et cela n'arrête pas
six paliers.
--- EN ---
fix_duplicate_index and restore_config_defaults were written, proven, and
absent from the driver. Every migration therefore rebuilt its redundant
indexes and lost its default pricelist again.
Measured on TWO independent 12 → 18 chains from the same source: 68
orphan relations, 9 languages with a NULL flag, one missing pricelist,
376 duplicate index pairs — the same numbers on both. Not a fluke of one
run: the path itself.
Indexes from step 17 onward: before that the convention had not changed.
Config defaults at the LAST step: the tool loads the registry, and only
the final state matters. Both with wait_at_error=False — with --apply,
exit 1 means "some remain", not "I crashed", and that must not halt six
steps.
Assisted-by: Claude Opus 5
2026-08-24 22:55:21 -04:00
" Odoo 17 changed the naming convention without dropping the old index: both are maintained on every write. This count is a cheap signal — the repair tool compares columns and uniqueness, and is the one to trust. " : {
" fr " : " Odoo 17 a changé la convention de nommage sans supprimer l ' ancien index : les deux sont entretenus à chaque écriture. Ce compte est un indicateur bon marché — l ' outil de réparation compare les colonnes et l ' unicité, c ' est lui qui fait foi. " ,
" en " : " Odoo 17 changed the naming convention without dropping the old index: both are maintained on every write. This count is a cheap signal — the repair tool compares columns and uniqueness, and is the one to trust. " ,
[ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.
« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.
Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.
Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.
--- EN ---
The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.
« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.
Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.
The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.
Assisted-by: Claude Opus 5
2026-08-24 07:21:37 -04:00
} ,
" Default pricelist missing while product is installed " : {
" fr " : " Liste de prix par défaut absente alors que product est installé " ,
" en " : " Default pricelist missing while product is installed " ,
} ,
" product.list0 was declared up to Odoo 16 only; nothing recreates it, and a quotation has no price list to pick. " : {
" fr " : " product.list0 n ' a été déclaré que jusqu ' à Odoo 16 ; rien ne le recrée, et un devis n ' a aucune liste de prix à choisir. " ,
" en " : " product.list0 was declared up to Odoo 16 only; nothing recreates it, and a quotation has no price list to pick. " ,
} ,
" Views bound to a model that no longer exists " : {
" fr " : " Vues liées à un modèle qui n ' existe plus " ,
" en " : " Views bound to a model that no longer exists " ,
} ,
" Opening one raises; the menu that leads to it is a dead end. " : {
" fr " : " En ouvrir une lève une erreur ; le menu qui y mène est un cul-de-sac. " ,
" en " : " Opening one raises; the menu that leads to it is a dead end. " ,
} ,
" Views inheriting a view that is gone " : {
" fr " : " Vues héritant d ' une vue disparue " ,
" en " : " Views inheriting a view that is gone " ,
} ,
" The whole inheritance chain below them stops rendering. " : {
" fr " : " Toute la chaîne d ' héritage en dessous cesse de s ' afficher. " ,
" en " : " The whole inheritance chain below them stops rendering. " ,
} ,
" External ids pointing at a model that is gone " : {
" fr " : " Identifiants externes pointant un modèle disparu " ,
" en " : " External ids pointing at a model that is gone " ,
} ,
" Every module update that resolves one of them fails. " : {
" fr " : " Chaque mise à jour de module qui en résout un échoue. " ,
" en " : " Every module update that resolves one of them fails. " ,
} ,
" Attachments whose carrying field is gone " : {
" fr " : " Pièces jointes dont le champ porteur a disparu " ,
" en " : " Attachments whose carrying field is gone " ,
} ,
" Odoo raises a KeyError merely checking them, and nothing will ever read them again. " : {
" fr " : " Odoo lève un KeyError rien qu ' en les contrôlant, et plus rien ne les lira jamais. " ,
" en " : " Odoo raises a KeyError merely checking them, and nothing will ever read them again. " ,
} ,
[ADD] analyse: l'état d'une instance, lu pour l'usage qu'on en fait
Le même chiffre veut dire deux choses opposées. Zéro cron actif est le
succès attendu d'une copie et une panne totale sur une production. Un
rapport qui ignore cela crie au loup sur ce qu'on vient de demander, et
l'on cesse de le lire. L'attente est donc déclarée, copy ou live, et
chaque contrôle dit ce qu'il juge sous l'une et sous l'autre.
Deux contrôles ont été ÉCARTÉS sous copy après mesure : sur la base 12
d'origine, jamais démarrée, 11 crons étaient déjà en retard et db_backup
déjà vide — notre propre update_prod_to_dev les efface. Les afficher en
rouge aurait été du bruit ; en vert, un mensonge. Ils sont montrés non
jugés, avec la raison.
Le code Python en base a été mesuré et abandonné : 132 actions serveur,
zéro citant un modèle inexistant, et les 3 « modèles sans table » sont
ir.autovacuum et deux autres modèles abstraits d'Odoo.
--- EN ---
The same number means two opposite things. Zero active cron is the
expected success of a copy and a total outage on production. A report
that ignores this cries wolf over what was just requested, and stops
being read. The expectation is therefore declared, copy or live, and each
check states what it judges under either.
Two checks were DROPPED under copy after measuring: on the untouched 12
source database, 11 crons were already late and db_backup already empty —
our own update_prod_to_dev deletes them. Red would have been noise; green
a lie. They are shown unjudged, with the reason.
In-database Python was measured and dropped: 132 server actions, none
naming a missing model, and the 3 "models without a table" are
ir.autovacuum and two other Odoo abstract models.
Assisted-by: Claude Opus 5
2026-08-24 14:39:09 -04:00
" A local database " : {
" fr " : " Une base de données locale " ,
" en " : " A local database " ,
} ,
" State of the instance " : {
2026-08-24 14:40:25 -04:00
" fr " : " 🩺 État de l ' instance " ,
" en " : " 🩺 State of the instance " ,
[ADD] analyse: l'état d'une instance, lu pour l'usage qu'on en fait
Le même chiffre veut dire deux choses opposées. Zéro cron actif est le
succès attendu d'une copie et une panne totale sur une production. Un
rapport qui ignore cela crie au loup sur ce qu'on vient de demander, et
l'on cesse de le lire. L'attente est donc déclarée, copy ou live, et
chaque contrôle dit ce qu'il juge sous l'une et sous l'autre.
Deux contrôles ont été ÉCARTÉS sous copy après mesure : sur la base 12
d'origine, jamais démarrée, 11 crons étaient déjà en retard et db_backup
déjà vide — notre propre update_prod_to_dev les efface. Les afficher en
rouge aurait été du bruit ; en vert, un mensonge. Ils sont montrés non
jugés, avec la raison.
Le code Python en base a été mesuré et abandonné : 132 actions serveur,
zéro citant un modèle inexistant, et les 3 « modèles sans table » sont
ir.autovacuum et deux autres modèles abstraits d'Odoo.
--- EN ---
The same number means two opposite things. Zero active cron is the
expected success of a copy and a total outage on production. A report
that ignores this cries wolf over what was just requested, and stops
being read. The expectation is therefore declared, copy or live, and each
check states what it judges under either.
Two checks were DROPPED under copy after measuring: on the untouched 12
source database, 11 crons were already late and db_backup already empty —
our own update_prod_to_dev deletes them. Red would have been noise; green
a lie. They are shown unjudged, with the reason.
In-database Python was measured and dropped: 132 server actions, none
naming a missing model, and the 3 "models without a table" are
ir.autovacuum and two other Odoo abstract models.
Assisted-by: Claude Opus 5
2026-08-24 14:39:09 -04:00
} ,
" Neutralisation, scheduler, backups, queues and who is an administrator — read for the use you intend. " : {
" fr " : " Neutralisation, cadenceur, sauvegardes, files et qui est administrateur — lu pour l ' usage que vous en faites. " ,
" en " : " Neutralisation, scheduler, backups, queues and who is an administrator — read for the use you intend. " ,
} ,
" Several checks read tables no RPC session exposes, and the lateness of a job is computed in SQL. " : {
" fr " : " Plusieurs contrôles lisent des tables qu ' aucune session RPC n ' expose, et le retard d ' une tâche se calcule en SQL. " ,
" en " : " Several checks read tables no RPC session exposes, and the lateness of a job is computed in SQL. " ,
} ,
" A development copy (restored, neutralised) " : {
" fr " : " Une copie de développement (restaurée, neutralisée) " ,
" en " : " A development copy (restored, neutralised) " ,
} ,
" An instance in service " : {
" fr " : " Une instance en service " ,
" en " : " An instance in service " ,
} ,
[ADD] analyse: anonymiser une copie, sans IA et sans rien casser
Des mots pris dans une liste, des nombres tirés entre 0 et 1000, écrits
en SQL. Aucun modèle, aucun réseau — un test le vérifie sur les imports.
Le difficile n'est pas de remplacer, c'est de savoir ce qu'on n'a PAS le
droit de toucher. Mesuré sur une base 18 réelle : 505 champs `selection`
sont stockés en varchar, 2693 many2one sont des entiers, 194 textes sont
des jsonb par langue, 301 contraintes d'unicité attendent une collision.
« Tous les champs string » n'existe pas ; on croise ir_model_fields,
pg_attribute et pg_constraint, et aucune des trois ne suffit seule.
Trois pièges ont été trouvés en LANÇANT l'outil, pas en le relisant :
PostgreSQL refuse d'indexer un ARRAY[...] sans parenthèses,
res_partner.credit_limit est un jsonb qu'Odoo appelle float, et
crm_lead.probability porte un CHECK qui interdit 1000. Chaque fois
l'écriture a échoué et la base est restée intacte : une seule
transaction, tout ou rien.
Preuve sur copie jetable : empreinte du schéma identique, 848 tables,
6495 contraintes, arch_db et xmlid intacts, lang et many2one inchangés —
seules les colonnes visées ont changé.
--- EN ---
Words from a list, numbers drawn between 0 and 1000, written in SQL. No
model, no network — a test checks that on the imports.
The hard part is not replacing, it is knowing what must NOT be touched.
Measured on a real 18 database: 505 `selection` fields are stored as
varchar, 2693 many2one are integers, 194 texts are per-language jsonb,
301 unique constraints await a collision. "All string fields" does not
exist; we cross ir_model_fields, pg_attribute and pg_constraint, and none
of the three is enough alone.
Three traps were found by RUNNING it, not by rereading it: PostgreSQL
refuses to subscript a bare ARRAY[...], res_partner.credit_limit is a
jsonb Odoo calls float, and crm_lead.probability has a CHECK forbidding
1000. Each time the write failed and the database stayed intact: one
transaction, all or nothing.
Proof on a throwaway copy: identical schema fingerprint, 848 tables, 6495
constraints, arch_db and xmlids intact, lang and many2one unchanged —
only the targeted columns changed.
Assisted-by: Claude Opus 5
2026-08-25 00:43:50 -04:00
" Anonymise a copy " : {
" fr " : " 🎭 Anonymiser une copie " ,
" en " : " 🎭 Anonymise a copy " ,
} ,
" Replace names, texts and numbers with meaningless ones so the copy can be shared. No AI: words from a list, numbers drawn at random, written by SQL. " : {
" fr " : " Remplacer noms, textes et nombres par des valeurs sans intérêt, pour pouvoir partager la copie. Sans IA : des mots pris dans une liste, des nombres tirés au hasard, écrits en SQL. " ,
" en " : " Replace names, texts and numbers with meaningless ones so the copy can be shared. No AI: words from a list, numbers drawn at random, written by SQL. " ,
} ,
" This one WRITES. It is offered only for a database you restored here — never for an instance in service. " : {
" fr " : " Celle-ci ÉCRIT. Elle n ' est offerte que pour une base restaurée ici — jamais pour une instance en service. " ,
" en " : " This one WRITES. It is offered only for a database you restored here — never for an instance in service. " ,
} ,
" This DESTROYS the data of " : {
" fr " : " Ceci DÉTRUIT les données de " ,
" en " : " This DESTROYS the data of " ,
} ,
" there is no undo. " : {
" fr " : " il n ' y a pas de retour en arrière. " ,
" en " : " there is no undo. " ,
} ,
" Type the database name to confirm (empty to cancel): " : {
" fr " : " Retapez le nom de la base pour confirmer (vide pour annuler) : " ,
" en " : " Type the database name to confirm (empty to cancel): " ,
} ,
" Cancelled: nothing was written. " : {
" fr " : " Annulé : rien n ' a été écrit. " ,
" en " : " Cancelled: nothing was written. " ,
} ,
" Hybrid: the default personal-data models, adjusted " : {
" fr " : " Hybride : les modèles à données personnelles par défaut, ajustés " ,
" en " : " Hybrid: the default personal-data models, adjusted " ,
} ,
" Whitelist: only the models I name " : {
" fr " : " Liste blanche : seulement les modèles que je nomme " ,
" en " : " Whitelist: only the models I name " ,
} ,
" Blacklist: every model except those I name " : {
" fr " : " Liste noire : tous les modèles sauf ceux que je nomme " ,
" en " : " Blacklist: every model except those I name " ,
} ,
" Models to ADD, comma separated (empty for none): " : {
" fr " : " Modèles à AJOUTER, séparés par des virgules (vide pour aucun) : " ,
" en " : " Models to ADD, comma separated (empty for none): " ,
} ,
" Models to EXCLUDE, comma separated: " : {
" fr " : " Modèles à EXCLURE, séparés par des virgules : " ,
" en " : " Models to EXCLUDE, comma separated: " ,
} ,
" A whitelist with no model would do nothing. " : {
" fr " : " Une liste blanche sans modèle ne ferait rien. " ,
" en " : " A whitelist with no model would do nothing. " ,
} ,
" Python file declaring MOTS (empty for the built-in): " : {
" fr " : " Fichier Python déclarant MOTS (vide pour la liste intégrée) : " ,
" en " : " Python file declaring MOTS (empty for the built-in): " ,
} ,
" Nothing to anonymise with these lists. " : {
" fr " : " Rien à anonymiser avec ces listes. " ,
" en " : " Nothing to anonymise with these lists. " ,
} ,
" column(s) " : {
" fr " : " colonne(s) " ,
" en " : " column(s) " ,
} ,
" dry run, nothing written " : {
" fr " : " marche à blanc, rien n ' est écrit " ,
" en " : " dry run, nothing written " ,
} ,
" text " : {
" fr " : " texte " ,
" en " : " text " ,
} ,
" numeric " : {
" fr " : " nombre " ,
" en " : " numeric " ,
} ,
" translated (jsonb) " : {
" fr " : " traduit (jsonb) " ,
" en " : " translated (jsonb) " ,
} ,
" unique " : {
" fr " : " unique " ,
" en " : " unique " ,
} ,
" Use --apply --confirm <database> to write. " : {
" fr " : " Utiliser --apply --confirm <base> pour écrire. " ,
" en " : " Use --apply --confirm <database> to write. " ,
} ,
" Replace the data of a COPY with meaningless data. " : {
" fr " : " Remplacer les données d ' une COPIE par des données sans intérêt. " ,
" en " : " Replace the data of a COPY with meaningless data. " ,
} ,
" comma separated, adds to the mode " : {
" fr " : " séparés par des virgules, s ' ajoutent au mode " ,
" en " : " comma separated, adds to the mode " ,
} ,
" comma separated, removed from it " : {
" fr " : " séparés par des virgules, retirés du mode " ,
" en " : " comma separated, removed from it " ,
} ,
" python file declaring MOTS " : {
" fr " : " fichier python déclarant MOTS " ,
" en " : " python file declaring MOTS " ,
} ,
" repeat the database name; --apply refuses without it " : {
" fr " : " répéter le nom de la base ; --apply refuse sans lui " ,
" en " : " repeat the database name; --apply refuses without it " ,
} ,
" Refusing to write: --confirm must repeat " : {
" fr " : " Écriture refusée : --confirm doit répéter " ,
" en " : " Refusing to write: --confirm must repeat " ,
} ,
" Nothing was written: " : {
" fr " : " Rien n ' a été écrit : " ,
" en " : " Nothing was written: " ,
} ,
" This file declares no MOTS: " : {
" fr " : " Ce fichier ne déclare aucun MOTS : " ,
" en " : " This file declares no MOTS: " ,
} ,
" Unknown mode: " : {
" fr " : " Mode inconnu : " ,
" en " : " Unknown mode: " ,
} ,
2026-03-07 02:59:07 -05:00
}
2026-03-10 04:07:38 -04:00
def get_lang ( ) - > str :
2026-03-07 02:59:07 -05:00
global _current_lang
if _current_lang is not None :
return _current_lang
# 1. Check env_var.sh file
2026-03-10 03:45:11 -04:00
if os . path . exists ( ENV_VAR_FILE ) :
2026-03-07 02:59:07 -05:00
try :
2026-03-10 03:45:11 -04:00
with open ( ENV_VAR_FILE ) as f :
2026-03-07 02:59:07 -05:00
content = f . read ( )
match = re . search (
r ' ^EL_LANG=[ " \' ]?( \ w+)[ " \' ]? ' , content , re . MULTILINE
)
if match :
lang = match . group ( 1 )
if lang in ( " fr " , " en " ) :
_current_lang = lang
return _current_lang
except OSError :
pass
# 2. Check env var
env_lang = os . environ . get ( " EL_LANG " )
if env_lang in ( " fr " , " en " ) :
_current_lang = env_lang
return _current_lang
# 3. Default
_current_lang = " fr "
return _current_lang
2026-03-10 04:07:38 -04:00
def set_lang ( lang : str ) - > None :
2026-03-07 02:59:07 -05:00
global _current_lang
_current_lang = lang
# Persist to env_var.sh
2026-03-10 03:45:11 -04:00
if os . path . exists ( ENV_VAR_FILE ) :
2026-03-07 02:59:07 -05:00
try :
2026-03-10 03:45:11 -04:00
with open ( ENV_VAR_FILE ) as f :
2026-03-07 02:59:07 -05:00
content = f . read ( )
except OSError :
return
new_line = f ' EL_LANG= " { lang } " '
if re . search ( r " ^EL_LANG= " , content , re . MULTILINE ) :
content = re . sub (
r ' ^EL_LANG=[ " \' ]? \ w*[ " \' ]? ' ,
new_line ,
content ,
count = 1 ,
flags = re . MULTILINE ,
)
else :
content = content . rstrip ( " \n " ) + " \n " + new_line + " \n "
2026-03-10 03:45:11 -04:00
with open ( ENV_VAR_FILE , " w " ) as f :
2026-03-07 02:59:07 -05:00
f . write ( content )
2026-03-10 04:07:38 -04:00
def lang_is_configured ( ) - > bool :
2026-03-07 02:59:07 -05:00
""" Check if a language has been explicitly set. """
2026-03-10 03:45:11 -04:00
if os . path . exists ( ENV_VAR_FILE ) :
2026-03-07 02:59:07 -05:00
try :
2026-03-10 03:45:11 -04:00
with open ( ENV_VAR_FILE ) as f :
2026-03-07 02:59:07 -05:00
content = f . read ( )
return bool ( re . search ( r " ^EL_LANG= " , content , re . MULTILINE ) )
except OSError :
pass
return False
2026-03-10 04:07:38 -04:00
def t ( key : str ) - > str :
2026-03-07 02:59:07 -05:00
entry = TRANSLATIONS . get ( key )
if entry is None :
return key
lang = get_lang ( )
return entry . get ( lang , entry . get ( " fr " , key ) )