- Après exécution d'une commande choisie dans la télémétrie, on propose de
REVENIR (r) ou de quitter. En revenant, la vue ET la position du curseur
sont RESTAURÉES (chemin porté par chaque nœud/carte ; run_tui prend/rend un
`state`).
- Vue Kanban : F4 fait défiler la DISPOSITION —
columns (une rangée de colonnes) / swimlanes (une rangée par menu de
niveau 1) / grid (grille 3 colonnes). F3 bascule Arbre/Kanban.
Validé headless : F3/F4 cyclent les dispositions, sélection -> action+état,
relance avec `state` -> curseur restauré sur le bon nœud.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Ticks ASYNCHRONES : les I/O bloquantes (lecture des logs, stat disque,
subprocess « virsh list », /proc) sont déportées en THREAD via
asyncio.to_thread ; seules les mises à jour d'UI restent sur la boucle
d'événements Textual -> plus de gel, même sous forte charge ou disque lent.
_collect_table (thread) rassemble statut+disque+télémétrie, l'application
au tableau se fait ensuite sur la boucle. _tick_log/_tick_domstate idem.
Helper _read_new (lecture incrémentale) réutilisé.
- Touche « w » : si aucun navigateur CLI n'est présent, on PROPOSE de
l'installer selon l'OS (apt Ubuntu/Debian, dnf Fedora, pacman Arch — nos 4
systèmes) : la commande est AFFICHÉE puis exécutée après validation (o/N).
Validé headless : ticks async mettent l'UI à jour ; browser_install_command
renvoie la bonne commande selon l'OS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La télémétrie n'affichait que les chemins effectivement visités. Elle
observe désormais les MÉTADONNÉES DE NAVIGATION issues du CODE : todo.py est
analysé (AST) pour reconstruire l'arbre RÉEL des menus et commandes.
- build_code_tree() lit _MENU_LABELS + les listes « choices » + le dispatch
« status == N: self.X() » de chaque menu pour bâtir l'arborescence complète
(sous-menus = cibles présentes dans _MENU_LABELS ; sinon commandes-feuilles,
libellées par leur prompt_description).
- Le TUI affiche cet arbre COMPLET (tous les menus, même jamais visités) avec
le compteur de visites en surimpression sur chaque menu (via la télémétrie
persistée). Repli sur l'arbre des seuls chemins visités si l'analyse échoue.
Validé : l'arbre reconstruit couvre Execute -> Deploy -> SSH/QEMU (+ toutes
leurs commandes : Resize, Delete, …), Git -> Git local server, GPT code ->
RTK, etc. ; compteurs corrects (menus visités > 0, autres 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nouvelle entrée « [5] Télémétrie de navigation (TUI) » dans le menu principal.
- Enregistrement : Todo._menu_header (seul constructeur du fil d'Ariane)
appelle todo_telemetry.record(fil) à chaque affichage de menu. Dédup des
ré-affichages consécutifs -> on ne compte que les TRANSITIONS de
navigation. Persistant dans ~/.erplibre/todo_telemetry.json. Best-effort :
ne casse jamais la navigation.
- Visualisation : todo_telemetry.run_tui() ouvre un TUI Textual affichant
l'ARBRE des fonctionnalités visitées (chaque nœud = un menu, avec son
nombre de visites), trié par usage décroissant. Touches : q (quitter),
r (réinitialiser), e (tout déplier). Sommaire : total navigations + menus.
Validé headless : enregistrement + dédup, arbre imbriqué à compteurs,
montage du TUI, peuplement, touches.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pendant le suivi, une VM pouvait « stagner » sans qu'on sache pourquoi
(mise en pause, ou effacée à côté). Le dashboard interroge désormais l'état
libvirt (« virsh list --all ») à INTERVALLE LENT (toutes les 10 s, un seul
appel pour tout le parc ; le tableau applique le cache à chaque tick de 2 s) :
- VM en pause (virsh suspend) -> État « ⏸ pause » (non terminal, peut
reprendre).
- VM absente de virsh (effacée pendant l'attente) -> État « ❌ effacée »,
terminal (on cesse de lire son log).
Sinon on garde le statut basé sur le log (⏳ / ✅ / ❌).
Validé headless : running -> ⏳, paused -> ⏸ pause, absente -> ❌ effacée.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nouvelle entrée dans le menu QEMU/KVM (section Gérer). Elle :
- affiche le disque principal (qcow2 via domblklist) + « qemu-img info »
(taille virtuelle + réelle) et l'état de la VM ;
- demande +NG (agrandir), -NG (réduire) ou NG (taille cible) ;
- applique : agrandissement À CHAUD si la VM tourne (virsh blockresize),
sinon qemu-img resize ; réduction via qemu-img resize --shrink (VM éteinte
obligatoire + avertissement fort : le FS invité n'est PAS réduit, risque
de perte de données) ;
- propose ensuite d'étendre le FS invité via SSH (growpart + resize2fs /
xfs_growfs / btrfs, device et type de FS détectés).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ajouts au suivi d'installation (dashboard Textual) :
- Télémétrie hôte (barre dédiée, MAJ 2 s) : CPU % = charge / nb CPU, et
disque du dossier des images (utilisé/total/libre) — utile pour anticiper
le remplissage du disque (cas vécu).
- Colonne « Disque » par VM : taille RÉELLE du qcow2 (st_blocks) -> on voit
quelle VM grossit.
- ETA « hypothèse » : les durées d'install sont MÉMORISÉES par architecture
dans ~/.erplibre/qemu-install/stats.json ; l'ETA du parc = médiane
historique (par arch, repli global) - temps écoulé, affichée au sous-titre.
- Touche « w » : ouvre l'UI web de la VM (Odoo :8069) dans un navigateur CLI
(browsh/carbonyl/w3m/links/elinks/lynx, le 1er trouvé ; sinon notifie quoi
installer). Surtout utile quand l'install est terminée.
Helpers module : load_stats/record_duration/eta_reference, _fmt_size/_fmt_secs,
vm_disk_path/disk_actual_size, cli_browser. run_monitor(run_app=False) expose
l'app pour test headless.
Validé headless (run_test) : montage, ticks, enregistrement des durées, ETA,
télémétrie, touches f/w — sans crash.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Encore du lag à 16 VM : update_cell était appelé pour CHAQUE ligne à chaque
tick (état + « Durée »), or la Durée — dérivée d'un `started` global — est
identique sur toutes les lignes et se réécrivait sans cesse -> re-render
permanent de la DataTable.
- _set_cell : n'appelle update_cell (donc ne re-render) que si la valeur
CHANGE. Mesuré : 312 -> 18 update_cell sur 10 ticks × 16 VM.
- La durée « live » passe dans le SOUS-TITRE (une seule mise à jour) ; la
colonne Durée n'est écrite qu'à la complétion (durée finale figée).
- Le NOMBRE de VM est affiché : titre « … (N VM) », sous-titre
« k/N terminées · mm:ss ».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le TUI de suivi laggait à 30 VM et se figeait quand l'I/O ralentissait
(disque plein / logs effacés). Cause : à chaque tick (1 s) on lisait le
fichier log ENTIER de CHAQUE VM pour le statut + on relisait tout le log
sélectionné, en synchrone sur la boucle d'événements Textual.
Corrections :
- read_status ne lit que les 4 derniers Ko (le marqueur de sortie est sur
la dernière ligne) : un log de 2 Mo passe de « tout lire » à 0,1 ms.
- _load_selected_log lit de façon INCRÉMENTALE (seek à l'offset) au lieu de
relire tout le fichier.
- Les VM TERMINÉES ne sont plus relues (cache _final).
- _tick_table (2 s) et _tick_log (1 s) séparés et PROTÉGÉS par try/except :
une erreur I/O transitoire (disque plein, log supprimé) ne tue plus la
boucle -> l'interface reste réactive aux touches.
- RichLog borné (max_lines=5000).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sur un gros parc (30 VM, ~18 émulées), la résolution d'IP « bloquait » 10 min
en silence sur les VM lentes. Trois causes traitées :
- Joignabilité par PING (ICMP) d'abord, TCP:22 en repli. Le ping répond dès
que le réseau de la VM est up, BIEN AVANT sshd : on ne retenait pas une VM
qui a déjà son IP juste parce que sshd (lent en émulation) n'était pas prêt.
Le ping distingue toujours le bail actif (répond) du bail périmé (non).
- IP cherchée sur PLUSIEURS sources : lease (dnsmasq), agent (qemu-guest-
agent DANS la VM) et arp (table ARP hôte). Le bail dnsmasq peut être vide
sous forte charge alors que la VM a une IP (constaté sur ce parc).
- BATTEMENT toutes les 30 s listant les VM encore en attente + timeout PAR VM
ramené à 5 min (au lieu de 10) dans la phase de résolution -> plus de
silence prolongé, et on n'attend pas indéfiniment une VM sans IP.
NB : déployer la matrice complète (30 VM dont ~18 émulées TCG) sature un seul
hôte ; certaines VM émulées n'obtiennent pas de bail DHCP à temps (limite de
capacité, pas un bug). La résolution dégrade désormais proprement.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Améliore le suivi du déploiement multi-VM :
- Compteur de COMPLÉTION devant chaque résultat (« [1/26] », « [2/26] »… =
ordre où les tâches TERMINENT), en plus de l'ID de préparation stable
(« [7/26] ») — utile car l'exécution parallèle rend les résultats
désordonnés.
- Temps d'exécution PAR ÉTAPE : durée de chaque VM (déploiement, résolution
d'IP) + bilan de phase (« Bilan déploiement : 24 OK, 2 en échec, 26 VM,
3m10s » ; « IP résolues : 25/26 (2m). »).
- SOMMAIRE TOTAL encadré en fin de flux (VM déployées, total avec
existantes, temps total).
Helper _fmt_dur (« 45s » / « 2m05s »).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chaque question oui/non affiche désormais explicitement la valeur par défaut
appliquée si on laisse vide, ex. :
« Suivi interactif (dashboard) ? (o/N, défaut : non) : »
Les 18 prompts passent par _is_yes(input(...)) : une réponse vide vaut NON.
On l'indique donc dans les traductions (fr « (o/N, défaut : non) », en
« (y/N, default: no) »). Seules les VALEURS i18n changent ; les clés (donc
les appels t(...) dans todo.py) sont inchangées.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sur un gros parc (ex. 26 VM), on ne voyait ni combien de VM allaient être
déployées ni lesquelles étaient en cours (résultats parallèles dans le
désordre). Deux améliorations :
- Le prompt de parallélisme affiche le NOMBRE de VM à déployer :
« Déploiements en parallèle (défaut : 24, 26 VM) : ». Pour cela, la
séparation à-créer / déjà-existantes est faite AVANT le prompt (on connaît
le vrai compte).
- Chaque VM reçoit un ID « k/N » suivant l'ordre de préparation, affiché
dans les résultats de déploiement (« ✅ [3/26] nom ») et dans la résolution
d'IP (« [5/30] nom : ip »). L'ID reste stable même si les résultats
reviennent dans le désordre -> on suit lesquelles sont traitées.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le menu QEMU avait deux commandes de déploiement redondantes : [1] « Déployer
une VM » (mono, noms/ressources personnalisés) et [4] « Déployer l'infra »
(multi, matrice distro×version×archi). Elles sont FUSIONNÉES en une seule,
[1] « Déployer une ou plusieurs VM » (_qemu_deploy), qui gère aussi bien 1 VM
que N, avec :
- renommage GRANULAIRE des VM à la demande (_qemu_customize_names) : noms
auto par défaut, on en renomme certains par numéros séparés de virgules ;
- multiplicateur de ressources (x1..x4) déjà présent ;
- aperçu dry-run ([2]) qui passe par le même flux et imprime les commandes
deploy_qemu (--dry-run) via le helper partagé _qemu_build_deploy_parts.
Menu simplifié : [1] Déployer une/plusieurs VM, [2] Aperçu (dry-run),
[3] Télécharger une image, puis Gérer/Catalogue renumérotés. Code mort retiré
(_qemu_deploy_vm, _qemu_prompt_arch, _normalize_disk_size,
_qemu_offer_ssh_config).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le menu Deploy listait 11 entrées « SSH - … » à plat, ce qui le surchargeait.
Elles sont déplacées dans un sous-menu « SSH (hôte distant)… »
(prompt_execute_deploy_ssh), comme le sous-menu QEMU. Le menu Deploy tient
désormais en 5 entrées :
── Local ──
[1] Cloner ERPLibre localement
[2] Configurer sshfs
── Distant & services ──
[3] SSH (hôte distant)… -> sous-menu (11 opérations SSH)
[4] NTFY
[5] QEMU/KVM…
Le fil d'Ariane affiche « TODO › Execute › Deploy › SSH » dans le sous-menu.
Les libellés et actions SSH sont inchangés (traductions réutilisées).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Avant le déploiement infra, on demande un multiplicateur de ressources :
x1 (minimum catalogue) .. x4. Il multiplie la RAM (base = minimum de la
version) et les vCPU (base 2), en bornant les vCPU au nombre de cœurs de
l'hôte et en signalant si la RAM totale dépasse la RAM libre.
- Le prompt affiche les ressources de l'hôte (vCPU, RAM libre) et, pour
chaque multiplicateur, le total RAM + vCPU/VM avec un ⚠ si ça dépasse.
- Le plan reflète les ressources effectives (vCPU + RAM×mult par VM).
- Les jobs passent --memory et --vcpus effectifs à deploy_qemu (avant,
l'infra laissait le minimum catalogue + 2 vCPU par défaut).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Après un déploiement infra, la résolution d'IP tournait EN SÉRIE (boucle
ssh_config puis install), jusqu'à 10 min par VM émulée, SANS aucune sortie :
le dashboard « n'ouvrait jamais » (impression de blocage), d'autant qu'une
VM qui ne boote pas (IP jamais attribuée) bloquait tout le reste.
Fix : _qemu_resolve_ips résout les IP de toutes les VM EN PARALLÈLE, avec
progression affichée par VM, une seule fois, réutilisée pour ~/.ssh/config
ET l'installation (plus de double résolution). _qemu_install_erplibre_*
acceptent une IP/ip_map déjà résolue.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le log d'installation commence désormais par un en-tête identifiant
l'installation : date, VM, distribution + version, architecture, branche, IP.
Écrit dès la création du log (plus jamais vide au démarrage).
_qemu_install_erplibre_monitored déduit distro/version/arch de chaque VM
(nom via _qemu_infra_name + arch via virsh dumpxml) et les passe à
launch_installs, qui écrit l'en-tête et les stocke dans le manifeste.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Menu « Déployer l'infra ERPLibre » : la sélection d'architecture propose
désormais [all] = toutes les architectures supportées. L'architecture
devient une dimension à part entière de la matrice de déploiement.
- [all] archis + [all] catalogue : une VM par (distro, version, archi
publiée par cette distro). Ex. ubuntu -> amd64/arm64/s390x, debian/fedora
-> amd64/arm64, arch -> amd64 (30 VM au total avec le catalogue courant).
- [all] archis + [granulaire] : la liste à plat inclut l'archi ([amd64]/
[arm64]/[s390x]) ; on choisit des combinaisons précises par virgules.
- [all] archis + [principal] : version par défaut de chaque distro × chaque
archi supportée.
- Une archi précise (arm64/s390x) restreint le catalogue aux distros qui la
publient (inchangé).
selected porte l'archi par élément ; les noms sont suffixés par archi pour
les non-natives (erplibre-ubuntu-2604-s390x) -> pas de collision. --arch
transmis par VM. Chaque distro ne reçoit que les archis QU'ELLE publie.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Symptôme : ~/.ssh/config (et l'install) pointaient sur .31 alors que la VM
était joignable sur .32 -> SSH KO (« No route to host »), console OK.
Cause : au boot, une image cloud demande d'abord une IP DHCP avec son
hostname par défaut (« ubuntu ») -> 1er bail ; puis cloud-init fixe le vrai
hostname et le client redemande -> 2e bail (IP différente). La MÊME MAC a
donc DEUX baux ; le 1er (« ubuntu ») devient périmé. Le code retenait
aveuglément le PREMIER IPv4 de « virsh domifaddr » = le bail périmé. Plus
visible sur s390x/arm64 émulé (boot lent -> les deux baux coexistent).
Fix :
- todo._qemu_vm_ip : renvoie en priorité l'IP dont le bail dnsmasq porte le
hostname == nom de la VM (bail définitif), sinon une IP JOIGNABLE (sshd up,
test TCP:22), sinon le dernier bail — jamais le 1er au hasard. Attente
portée à 10 min (boot émulé lent). Helpers _qemu_lease_candidates /
_qemu_ip_reachable / _qemu_lease_ip_for_host.
- deploy_qemu.wait_for_ip : même logique (IP joignable, sinon la plus
récente) pour l'IP affichée en fin de déploiement standalone.
Validé sur la VM s390x réelle : candidats [.31, .32] -> choisit .32
(hostname-match + seule joignable) ; ssh via alias OK.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1) Nommage : _qemu_infra_name ajoute un suffixe d'architecture quand elle
diffère de la native de l'hôte -> déployer un s390x sur un hôte amd64
donne « erplibre-ubuntu-2604-s390x » (au lieu de « erplibre-ubuntu-2604 »).
Évite les collisions de noms entre archis et rend l'archi visible. Passé
dans les 3 appelants (VM unique + plan + jobs de l'infra).
2) Log d'installation vide : sur une archi ÉMULÉE (s390x/arm64 sur x86) le
boot prend plusieurs minutes ; pendant ce temps l'attente n'écrivait rien
-> le log restait VIDE et paraissait « bloqué ». _launch_one écrit
désormais un en-tête d'attente immédiat + un battement toutes les ~30 s,
et l'attente sshd/cloud-init passe de ~12 à ~20 min (boot émulé lent).
_qemu_wait_ssh (chemin streamé) : timeout porté à 20 min également.
Validé : nommage (amd64/natif sans suffixe, s390x/arm64 suffixés) ; le log
se remplit dès le départ (en-tête + « ... 30s »).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64
(x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) :
- arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet).
- s390x : Ubuntu seulement (inchangé).
deploy_qemu.py :
- host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE
(TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée.
- virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware
AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé.
- ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware
UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de
paquets, avec vérif de présence (binaire + firmware pour arm64).
- Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro
ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias
(fedora/arch aarch64 ; ubuntu/debian arm64).
todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/
s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un *
(défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est
restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora,
s390x -> ubuntu).
install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré
best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install).
Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install
aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et
filtrage simulés (natif *, arm64 par distro).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Menu « Déployer l'infra ERPLibre » :
- Nouveau choix [granulaire] dans la sélection des distributions : affiche
la liste À PLAT de TOUTES les versions du catalogue (distro + version,
défauts marqués d'un *) et permet d'en choisir plusieurs par leurs numéros
séparés par des virgules (ex. « 1,3,12 »). Complète [all] et [principal].
- Demande l'ARCHITECTURE du parc en début de flux, par défaut l'architecture
NATIVE de l'hôte (amd64/arm64/s390x via uname -m). s390x est proposé mais
émulé (lent) et n'a d'images cloud que pour Ubuntu -> le catalogue est
alors restreint à Ubuntu (les autres distros sont ignorées avec un
avertissement). --arch est transmis à deploy_qemu.py quand ≠ amd64.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Après le passage à pacman, l'init PostgreSQL échouait :
« /usr/bin/postgres: /usr/lib/libm.so.6: version GLIBC_2.44 not found ».
Cause : Arch est en rolling release et NE SUPPORTE PAS les mises à jour
partielles. Sur une image cloud dont la glibc date du build de l'image, un
« pacman -S <paquet récent> » (postgresql 18.4) installe un binaire lié à
une glibc plus récente que celle du système -> symbole introuvable.
Fix : mise à jour COMPLÈTE (pacman -Syu) avant d'installer, dans les deux
endroits : install_arch_linux.sh (deps OS) et le bootstrap Arch de
_qemu_erplibre_remote_cmd (todo.py, remplace -Syy par -Syu).
Validé sur VM Arch réelle : après -Syu, « postgres (PostgreSQL) 18.4 » OK,
initdb OK, service postgresql actif, superuser erplibre créé, psql connecte.
Chaîne complète confirmée : gcc 16.1.1, make, node, psql tous présents.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deux échecs d'install ERPLibre sur VM constatés dans les logs :
Arch — install_arch_linux.sh utilisait « yay » (helper AUR ABSENT d'une
image cloud et qui refuse de tourner en root) et n'installait JAMAIS
base-devel : sur une image cloud fraîche rien ne s'installait, donc pas de
compilateur C -> pyenv : « no acceptable C compiler found » -> build de
Python 3.12 échoué -> pas de venv -> pip/poetry/repo absents. Réécrit avec
pacman (dépôts officiels) : base-devel (gcc/make), deps de build pyenv
(openssl/zlib/xz/tk/…), PostgreSQL (+ initdb, Arch ne l'initialise pas),
Node/npm, deps Odoo en best-effort paquet par paquet (pacman refuse toute
la transaction sur un seul nom inconnu). wkhtmltopdf (AUR) : ignoré
proprement.
Fedora — « Connection closed by remote host » (exit 255) dès le début :
« cloud-init status --wait » tournait dans une session SSH UNIQUE tuée
quand cloud-init régénère les clés d'hôte et REDÉMARRE sshd au 1er boot.
On attend désormais la FIN de cloud-init via des connexions COURTES
successives (chaque tentative survit à un redémarrage de sshd), AVANT de
lancer l'install — dans les deux chemins : _qemu_wait_ssh (streamé) et
_launch_one (détaché/monitoré). On matche sur le TEXTE de « cloud-init
status » (done/disabled/error/degraded) car son code de sortie n'est pas
fiable selon la version.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ajoute --arch s390x au déploiement QEMU. Réalité constatée (juillet 2026) :
seul Ubuntu publie des images cloud s390x (cloud-images.ubuntu.com : OK) ;
Debian et Fedora n'en publient pas (404), Arch ne cible que x86_64/aarch64.
s390x est donc scopé à Ubuntu et rejeté proprement pour les autres distros.
deploy_qemu.py :
- FOREIGN_ARCHES / S390X_DISTROS : s390x n'est valide que pour Ubuntu.
- virt_install : sur s390x -> --arch s390x, --machine s390-ccw-virtio,
--virt-type qemu (émulation TCG, pas de KVM sur hôte x86), console SCLP
(pas de série ISA), amorçage IPL/zipl (aucun --boot UEFI/BIOS). Les
disques/réseau bus=virtio sont mappés en virtio-ccw par libvirt.
- ensure_s390x_emulator : installe qemu-system-s390x au besoin
(apt: qemu-system-misc, dnf: qemu-system-s390x, pacman:
qemu-emulators-full, zypper: qemu-s390).
- Avertit que s390x est ÉMULÉ (lent) sur un hôte x86.
todo.py : le menu « Déployer une VM » demande l'architecture (s390x proposé
uniquement pour Ubuntu, avec avertissement lenteur) et passe --arch.
install_debian_dependency.sh : wkhtmltopdf n'a pas de build s390x -> skip
best-effort au lieu d'avorter l'install (la branche s390x rust-all existait
déjà pour compiler les wheels).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fedora install stalled: the log stopped right after boot with no exit
marker. Cause: cloud-init was running "dnf -y install qemu-guest-agent
openssh-server" at first boot; on this host's slow VM network that dnf
took many minutes (metadata refresh + download), so cloud-init stayed
"running" and the install (which waits for cloud-init) never proceeded.
Fix: do NOT install any package via cloud-init. sshd is already present
in every cloud image (verified Ubuntu/Debian/Fedora/Arch) and is just
enabled via runcmd; the tools (curl/git/make…) are installed by the
ERPLibre bootstrap with optimised mirrors. cloud-init now finishes in
seconds (Fedora: "status: done" at 10s instead of blocking 5+ min).
--package still adds a cloud-init packages block on demand.
Also: when ERPLibre is installed via the infra menu, add
ERPLIBRE_EXTRA_DISK_GB (5G) to each VM's disk, because the image minimum
left only ~97 MB free after installation. Drop the now-useless
"--package git" (the bootstrap installs git).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Arch install failed at once: "error: failed to synchronize all
databases (unable to lock database)" then "reflector: command not
found". Cause: cloud-init was still running its own pacman (installing
qemu-guest-agent/openssh) and held /var/lib/pacman/db.lck when our
bootstrap started, so every pacman call failed and reflector never got
installed.
Fix: the remote install now waits for cloud-init to finish first
("cloud-init status --wait", 15 min timeout) before touching any package
manager — this releases the apt/dnf/pacman lock cloud-init holds during
its package stage (helps all distros). For Arch, also drop a stale
db.lck when no pacman is actually running. Verified live: cloud-init
done, no lock, reflector + refresh + curl/git/make all succeed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dashboard: the info bar under the log now shows the selected VM's log
file path (in addition to its ssh command), so it can be opened/shared.
Install bootstrap: each package-manager branch first refreshes the repos
so the VM is as fast as possible, and Arch (pacman) is now supported:
- apt: apt-get update
- dnf: dnf makecache (dnf5 picks the fastest mirrors)
- pacman (Arch): reflector selects the 20 fastest HTTPS mirrors ->
pacman -Syy -> install curl/git/make (Arch was previously unsupported
by the bootstrap and would abort with "no package manager").
Validated live on the Arch VM: reflector + refresh + install all OK.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Arch (rolling release, single "latest" version) joins ubuntu/debian/
fedora: official cloud image geo.mirror.pkgbuild.com/images/latest/
Arch-Linux-x86_64-cloudimg.qcow2 (cloud-init included), osinfo=archlinux
(known locally), UEFI boot + virtio seed like the others. ERPLibre
already has install_arch_linux.sh. Exposed in the infra/deploy menus.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the QEMU install logs: Debian 12 succeeded, Ubuntu 24.04 and
Fedora 42 failed.
Ubuntu (install_debian_dependency.sh):
- apt failed with "Could not get lock" (cloud-init/unattended-upgrades
hold it on first boot) -> all apt-get calls now use
DPkg::Lock::Timeout=600 so apt waits for the lock.
- "postgis" is not a package on Ubuntu 24.04, so the postgresql line
exited 1 before installing build-essential -> no C compiler -> pyenv
could not build Python 3.12.10. PostGIS is now best-effort (tries
postgis, then postgresql-postgis) and never aborts; postgresql-contrib
is added.
Fedora (new install_fedora_dependency.sh, wired into install_dev.sh):
- install_dev.sh dispatched Fedora to the apt script; it now has a
fedora / ID_LIKE branch calling a dnf-based dependency installer
(dev tools, postgresql-server + initdb, pyenv build deps, node, etc.),
using --refresh --skip-unavailable to tolerate mirror/name issues.
Bootstrap (todo.py): the dnf "curl git make" install hit a GPG/checksum
failure on a fresh image; it now uses --refresh and retries after
"dnf clean all".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After "Opening the interactive monitor..." the install now lists every
VM's log file path, so they can be read or shared even when leaving the
dashboard before the installs finish.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of "git/make: command not found": the bootstrap ran
"apt-get update -qq && apt-get install -y $PKGS". Inside an && list,
set -e does NOT abort on failure, so when apt-get update failed (flaky
VM network) the install was silently skipped and the script marched on
without git/make. Now: "apt-get update || true; apt-get install -y
$PKGS" (update best-effort, install mandatory), followed by an explicit
"command -v curl git make" check that exits with a clear message
("Outil manquant ... (reseau de la VM ?)") instead of a cryptic failure
later. Shared by the streamed and the monitored install paths.
Dashboard: add "c" to copy the selected VM's full log to the clipboard
(OSC 52, works over SSH) with a notification; the ssh bar documents
Shift+drag for native terminal selection; on close, print a ready-to-
copy "tail -n +1 <logdir>/*.log" to share the logs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deploying ERPLibre (single VM [1] and infra [4]) now asks "Interactive
monitoring dashboard?". When yes, the installs run DETACHED (setsid, one
log file + exit marker per VM) and a Textual dashboard opens:
- left: a table of VMs with live status (⏳ running / ✅ done / ❌ failed
with exit code) and elapsed time;
- right: the selected VM's log, tailed live (f toggles follow);
- footer: the VM's ssh command; s suspends the dashboard and SSHes in;
- q quits back to the menu — the installs keep running detached, so you
can leave before they finish and reopen later on the same log dir.
New module qemu_install_monitor.py holds the detached launcher and the
Textual app. The remote install script is factored into
_qemu_erplibre_remote_cmd. Falls back gracefully if textual is missing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Right after boot the VM's sshd may not be up yet (notably on Fedora),
so the install failed with "Connection refused". _qemu_install_erplibre_vm
now polls SSH (ssh ... true, BatchMode) for up to 3 minutes and skips
with a clear message if it never answers, instead of failing instantly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The infra distribution prompt now accepts "principal" (alongside
numbers and "all"): it deploys the default version of each distro —
ubuntu 24.04, debian 12, fedora 42 — one VM each. The default version is
also marked with a * in the distro listing so it is visible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Minimal cloud images ship without make (and often without curl), so the
ERPLibre install cloned the repo then died on "make: command not found".
The remote bootstrap now installs curl, git and make via apt (Debian/
Ubuntu) or dnf/yum (Fedora) — running apt-get update first since
cloud-init did not refresh the lists — before cloning and running
make install_os / make install_odoo_18.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"Deploy a new VM" now asks, after a successful deploy, whether to
install ERPLibre into ~/git/erplibre (pick a branch), just like the
infra command.
Installing ERPLibre no longer only clones the repo: after the clone it
runs "make install_os" then "make install_odoo_18" over SSH (the
erplibre user has passwordless sudo from cloud-init). The Odoo target is
a constant (ERPLIBRE_ODOO_TARGET) and both the single-VM and infra flows
use the same install path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cleanup command now runs, each listed and confirmed independently:
1. orphan files (disks/seeds/.part/nvram) — as before;
2. ghost domains: libvirt domains whose disk no longer exists ->
offer destroy + undefine --nvram;
3. stale codename-named Ubuntu images (noble/resolute/... left by the
move to /releases/ version-named images) -> targeted delete;
4. orphan ~/.ssh/config entries: "Host erplibre-*" blocks with no
matching VM (personal SSH hosts are never touched);
5. stale libvirt DHCP leases whose MAC belongs to no VM (best-effort
rewrite of the dnsmasq status file + SIGHUP; they also self-expire);
6. the full base-image cache remains an explicit opt-out at the end.
Detections verified read-only on the host.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New Manage entry that finds and lists QEMU leftovers with no matching
libvirt domain, then asks before deleting:
- working disks /var/lib/libvirt/images/<name>.qcow2
- cloud-init seeds .../iso/<name>-seed.iso
- interrupted downloads (*.part)
- orphan UEFI nvram files
Sizes are shown human-readable with a total. Cached base cloud images
(reusable) are listed and offered separately, since removing them forces
a re-download; this also cleans the stale codename-named duplicates left
by the switch to /releases/ version-named images.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three papercuts hit when creating a VM from the menu:
1. Disk size "30" was passed verbatim to qemu-img resize, which read it
as 30 BYTES and failed ("use --shrink"). The prompt now shows units
(e.g. 30G, 1T) and a bare number is normalised to GB ("30" -> "30G"),
in the menu and in deploy_qemu.py itself.
2. The VM name is no longer required: leaving it blank uses the default
erplibre-<distro>-<version> (e.g. erplibre-ubuntu-2604). Distro and
version are now asked first so the default can be offered.
3. The menu ignored the deploy exit code and carried on to "wait for the
VM IP" even after a failure, hanging forever with the error scrolled
off. It now checks the return code and stops with a clear message.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The infra "Parallel deployments" prompt defaulted to a fixed 4. It now
defaults to the host CPU count (os.cpu_count()), still capped by the
number of selected VMs, with a fallback of 4 when the count is unknown.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_write_ssh_config_entry used a DOTALL (?ms) regex to remove an existing
"Host <name>" block; with DOTALL, ".*" crossed newlines and matched from
the block to the end of the file, so re-adding an already-present host
deleted every entry after it. Dropping DOTALL (keep MULTILINE, match
indented lines with [^\n]*) removes only the target block and preserves
the rest. Not a parallelism issue — the writes are sequential.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Interim Ubuntu releases (24.10, 25.04) were removed from cloud-images
/current/ once EOL, so their image URL 404'd. Switch the Ubuntu image
URL to /releases/<version>/release/ubuntu-<version>-server-cloudimg-
<arch>.img, which stays available for every published release, and drop
the EOL interim versions from the catalogue (keep supported LTS + 25.10
+ 26.04).
Also:
- silence the "crypt is deprecated" DeprecationWarning (openssl fallback
already covers Python 3.13+);
- a 404 during download now reports "image not found (EOL/removed), pick
a supported LTS" instead of blaming connectivity;
- add a NoCloud network-config (DHCP on e*) to the seed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The infra fleet was deployed one VM at a time, each blocking up to 90s
waiting for its DHCP lease. Deploys now run concurrently through a
ThreadPoolExecutor (default min(count, 4), promptable), with each job's
output captured and printed per VM as it finishes (no interleaving). VMs
are created with --no-wait-ip so workers return quickly; IPs are then
collected once for the ~/.ssh/config step. Preferred over shelling out
to GNU parallel: no external dependency, grouped output, and it stays
integrated with the ssh-config and ERPLibre-clone steps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
VMs were created with user "erplibre" but NO password (lock_passwd:
true), so the serial console refused every login and only SSH-by-key
worked — hence "can't connect".
- The todo deploy flow (single VM and infra) now sets a default console
password "erplibre" for the "erplibre" user, in addition to the SSH
key, so virsh console and password SSH both work. It is shown as
"Console/SSH login: erplibre / erplibre" and can be changed at the
single-VM prompt.
- The console entry now prints the default login and the Ctrl+] hint.
- After creating a VM, offer to add it to ~/.ssh/config (Host <name>,
HostName <ip>, User erplibre) so "ssh <name>" just works; the infra
flow asks once and adds every VM. Existing blocks for the same Host
are replaced, the rest of the file is preserved, mode kept 600.
Note: VMs created before this change have no console password — delete
and redeploy them to get the erplibre/erplibre login.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Group the longer menus with section headers (reordering items so the
numbering stays sequential within each section):
- Execute: Development / Data / Sources & documentation / AI &
automation / Deployment, network & security / Preferences.
- QEMU/KVM: Deployment / Manage / Catalog.
- Database: Backup / Restore / Danger zone.
- RTK: Setup / Status / Optimize.
- Config: Generate / Advanced.
The QEMU config-entry lookup now skips section rows so appended
makefile entries still map to the right number.
Also in the QEMU menu:
- new "Open the console on a VM" entry (lists VMs, asks which, reminds
Ctrl+] to quit, runs virsh console);
- "Show a VM IP address" accepts "all" to print every VM's IP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fill_help_info() now renders {"section": "..."} entries as a header line
without consuming a number, so numbering stays continuous over the real
commands (the hardcoded elif chains keep matching). The Deploy menu is
split into "Local", "SSH (remote host)" and "Virtualization &
notifications", making the long SSH block easy to scan.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New QEMU/KVM menu entry to remove one, several or all VMs. It lists the
defined domains, lets you pick by number or "all", asks whether to also
delete the disk images (the qcow2 working disk + the seed ISO), shows
what will be removed and asks for confirmation. Each VM is powered off
(destroy) then undefined (--nvram, with a fallback for older virsh);
disks are only deleted when explicitly requested.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>