[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
"""Menu Proxmox VE : déployer et gérer des VM sur un hôte DISTANT.
|
|
|
|
|
|
|
|
|
|
|
|
Sorti de todo.py, qui passait 13 000 lignes. Toute la différence avec le menu
|
|
|
|
|
|
QEMU/KVM tient en une phrase : l'hyperviseur est ailleurs. On choisit donc
|
|
|
|
|
|
l'hôte, on le vérifie, et tout part par SSH — voir script/proxmox/README.md.
|
|
|
|
|
|
|
|
|
|
|
|
Mixin : ses méthodes vivent sur la classe TODO, elles peuvent donc appeler
|
|
|
|
|
|
librement les helpers généraux (self._is_yes, self.fill_help_info) et ceux du
|
|
|
|
|
|
menu QEMU (self._qemu_prompt_distro, self._qemu_install_erplibre_monitored),
|
|
|
|
|
|
qu'elles réutilisent volontairement plutôt que de les redire."""
|
|
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
|
import re
|
|
|
|
|
|
import shlex
|
[FIX] cache et journal : hôte imbriqué soustrait, décision écrite
Un invité sans magasin de confiance ne peut RIEN recevoir, et sur un hôte
Proxmox c'est l'HÔTE qu'il faut excepter : imbriqué, l'invité sort masqué
derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis
l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le
gestionnaire de paquets se rabattait sur 564 dérivations à construire.
Le journal par VM ne portait AUCUN message du menu : la cause vivait sur
une console qui défile, et le fichier qu'on rouvre après l'échec n'avait
que le symptôme. Et « db_drop_all » annonçait détruites des bases qui ne
l'étaient pas, son code de retour jeté.
--- EN ---
A guest with no trust store can receive NOTHING, and on a Proxmox host it
is the HOST that must be exempted: nested, the guest leaves masqueraded
behind it and the bridge never sees its own address. Measured from the
guest: code 000 and SSL verification 19, then 200 and 0. Without it the
package manager fell back to building 564 derivations.
The per-VM log carried NO message from the menu: the cause lived on a
console that scrolls away, and the file reopened after failure held only
the symptom. And "db_drop_all" announced databases as dropped that were
not, its exit status discarded.
Assisted-by: Claude Opus 5
2026-09-16 00:43:40 -04:00
|
|
|
|
import shutil
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
import subprocess
|
|
|
|
|
|
import time
|
|
|
|
|
|
|
2026-08-23 18:04:17 -04:00
|
|
|
|
import click
|
|
|
|
|
|
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
from script.todo import todo_prefs
|
[FIX] cache et journal : hôte imbriqué soustrait, décision écrite
Un invité sans magasin de confiance ne peut RIEN recevoir, et sur un hôte
Proxmox c'est l'HÔTE qu'il faut excepter : imbriqué, l'invité sort masqué
derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis
l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le
gestionnaire de paquets se rabattait sur 564 dérivations à construire.
Le journal par VM ne portait AUCUN message du menu : la cause vivait sur
une console qui défile, et le fichier qu'on rouvre après l'échec n'avait
que le symptôme. Et « db_drop_all » annonçait détruites des bases qui ne
l'étaient pas, son code de retour jeté.
--- EN ---
A guest with no trust store can receive NOTHING, and on a Proxmox host it
is the HOST that must be exempted: nested, the guest leaves masqueraded
behind it and the bridge never sees its own address. Measured from the
guest: code 000 and SSL verification 19, then 200 and 0. Without it the
package manager fell back to building 564 derivations.
The per-VM log carried NO message from the menu: the cause lived on a
console that scrolls away, and the file reopened after failure held only
the symptom. And "db_drop_all" announced databases as dropped that were
not, its exit status discarded.
Assisted-by: Claude Opus 5
2026-09-16 00:43:40 -04:00
|
|
|
|
from script.todo.qemu_privilege import virsh_argv
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
from script.todo.todo_i18n import t
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProxmoxMenuMixin:
|
|
|
|
|
|
"""Menu Proxmox VE : déployer et gérer des VM sur un hôte DISTANT."""
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------------------------------------------------------ #
|
|
|
|
|
|
# QEMU / KVM (libvirt) VM deployment
|
|
|
|
|
|
# ------------------------------------------------------------------ #
|
|
|
|
|
|
# ----------------------------------------------------------------- #
|
|
|
|
|
|
# Proxmox VE : l'hyperviseur est AILLEURS
|
|
|
|
|
|
# ----------------------------------------------------------------- #
|
|
|
|
|
|
# Toute la différence avec QEMU/KVM tient là : ici on n'exécute rien sur
|
|
|
|
|
|
# la machine locale. Il faut donc d'abord SAVOIR OÙ, et le retenir — sans
|
|
|
|
|
|
# quoi chacune des dix-sept commandes reposerait la question.
|
|
|
|
|
|
_PVE_PREF_KEY = "proxmox_host"
|
[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
|
|
|
|
# Le script qui transforme une Debian en hyperviseur. Autonome : il se
|
|
|
|
|
|
# laisse exécuter par un tube, sans être copié d'abord.
|
|
|
|
|
|
PVE_INSTALL_SCRIPT = "script/proxmox/install_proxmox.sh"
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
|
|
|
|
|
|
def _pve_host(self, ask=True):
|
|
|
|
|
|
"""Hôte Proxmox retenu, ou None. Demande au besoin.
|
|
|
|
|
|
|
|
|
|
|
|
Mémorisé dans les préférences : le menu compte dix-sept entrées, et
|
|
|
|
|
|
redemander l'hôte à chacune serait insupportable. Le choix reste
|
|
|
|
|
|
affiché en tête du menu, et se change par son entrée dédiée.
|
|
|
|
|
|
"""
|
|
|
|
|
|
cache = getattr(self, "_pve_host_cache", None)
|
|
|
|
|
|
if cache:
|
|
|
|
|
|
return cache
|
|
|
|
|
|
garde = todo_prefs.get(self._PVE_PREF_KEY) or {}
|
|
|
|
|
|
if garde.get("target"):
|
|
|
|
|
|
self._pve_host_cache = garde
|
|
|
|
|
|
return garde
|
|
|
|
|
|
return self._pve_pick_host() if ask else None
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_forget_host(self):
|
|
|
|
|
|
self._pve_host_cache = None
|
|
|
|
|
|
todo_prefs.set(self._PVE_PREF_KEY, {})
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_remember_host(self, host):
|
|
|
|
|
|
self._pve_host_cache = host
|
|
|
|
|
|
todo_prefs.set(self._PVE_PREF_KEY, host)
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _pve_label(host):
|
2026-09-16 00:42:31 -04:00
|
|
|
|
"""« root@hyperviseur (par rebond) », pour l'afficher en tête de
|
|
|
|
|
|
menu."""
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
if not host:
|
|
|
|
|
|
return ""
|
|
|
|
|
|
lab = host.get("target", "?")
|
|
|
|
|
|
if host.get("jump"):
|
|
|
|
|
|
lab += f" ({t('through')} {host['jump']})"
|
|
|
|
|
|
if host.get("version"):
|
|
|
|
|
|
lab += f" — PVE {host['version']}"
|
|
|
|
|
|
return lab
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_pick_host(self):
|
|
|
|
|
|
"""Choisit l'hôte Proxmox : VM locale, adresse, ou ~/.ssh/config."""
|
|
|
|
|
|
print(f"\n{t('Which Proxmox host?')}")
|
|
|
|
|
|
print(f" [1] {t('From the local QEMU VMs')}")
|
|
|
|
|
|
print(f" [2] {t('Type an address')}")
|
|
|
|
|
|
print(f" [3] {t('From ~/.ssh/config')}")
|
|
|
|
|
|
actuel = todo_prefs.get(self._PVE_PREF_KEY) or {}
|
|
|
|
|
|
if actuel.get("target"):
|
|
|
|
|
|
print(f" [4] {t('Keep')} : {self._pve_label(actuel)}")
|
|
|
|
|
|
choix = input(t("Choice: ")).strip()
|
|
|
|
|
|
if choix == "4" and actuel.get("target"):
|
|
|
|
|
|
self._pve_host_cache = actuel
|
|
|
|
|
|
return actuel
|
|
|
|
|
|
if choix == "1":
|
|
|
|
|
|
host = self._pve_host_from_qemu()
|
|
|
|
|
|
elif choix == "3":
|
|
|
|
|
|
host = self._pve_host_from_ssh_config()
|
|
|
|
|
|
elif choix == "2":
|
|
|
|
|
|
host = self._pve_host_manual()
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(t("Cancelled."))
|
|
|
|
|
|
return None
|
|
|
|
|
|
if not host:
|
|
|
|
|
|
return None
|
|
|
|
|
|
return self._pve_confirm_host(host)
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_host_manual(self):
|
|
|
|
|
|
"""Saisie libre. « root@ » par défaut : « qm » exige les privilèges."""
|
|
|
|
|
|
brut = input(t("Address (user@host, default user root): ")).strip()
|
|
|
|
|
|
if not brut:
|
|
|
|
|
|
print(t("Cancelled."))
|
|
|
|
|
|
return None
|
|
|
|
|
|
cible = brut if "@" in brut else f"root@{brut}"
|
|
|
|
|
|
jump = input(t("SSH jump host (blank = none): ")).strip()
|
|
|
|
|
|
return {"target": cible, "jump": jump}
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_host_from_qemu(self):
|
|
|
|
|
|
"""Une VM Proxmox déployée ICI, prise dans la liste libvirt.
|
|
|
|
|
|
|
|
|
|
|
|
C'est le cas du parc : on déploie une VM « proxmox » avec le menu
|
|
|
|
|
|
QEMU/KVM, puis on déploie DEDANS. L'IP est celle du bail DHCP, pas une
|
|
|
|
|
|
adresse à retaper.
|
|
|
|
|
|
"""
|
|
|
|
|
|
noms = self._qemu_list_domains()
|
|
|
|
|
|
if not noms:
|
|
|
|
|
|
print(f"\n{t('No VM found.')}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
print(f"\n{t('Local VMs:')}")
|
|
|
|
|
|
ips = {}
|
|
|
|
|
|
for i, nom in enumerate(noms, 1):
|
|
|
|
|
|
ip = self._qemu_vm_ip_now(nom) or ""
|
|
|
|
|
|
ips[nom] = ip
|
|
|
|
|
|
etat = self._qemu_domstate(nom)
|
|
|
|
|
|
print(f" [{i}] {nom:<32} {ip or '-':<16} {etat}")
|
|
|
|
|
|
sel = input(t("Selection (number): ")).strip()
|
|
|
|
|
|
if not sel.isdigit() or not 1 <= int(sel) <= len(noms):
|
|
|
|
|
|
print(t("Invalid selection!"))
|
|
|
|
|
|
return None
|
|
|
|
|
|
nom = noms[int(sel) - 1]
|
|
|
|
|
|
ip = ips.get(nom)
|
|
|
|
|
|
if not ip:
|
|
|
|
|
|
print(f" ⚠ {t('No IP for this VM: is it running?')}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
return {"target": f"root@{ip}", "jump": "", "vm": nom}
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_host_from_ssh_config(self):
|
|
|
|
|
|
"""Un alias de ~/.ssh/config : il porte déjà utilisateur, port et
|
|
|
|
|
|
ProxyJump — rien à redemander, et le rebond traverse."""
|
[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
|
|
|
|
entrees = self._ssh_config_entries(os.path.expanduser("~/.ssh/config"))
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
if not entrees:
|
|
|
|
|
|
print(f"\n{t('No SSH hosts found in ~/.ssh/config')}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
print()
|
|
|
|
|
|
for i, (nom, info) in enumerate(entrees, 1):
|
|
|
|
|
|
hn = info.get("hostname", nom)
|
|
|
|
|
|
u = info.get("user", "")
|
|
|
|
|
|
desc = nom + (f" ({hn})" if hn != nom else "")
|
|
|
|
|
|
print(f" [{i}] {desc}{f' [{u}]' if u else ''}")
|
|
|
|
|
|
sel = input(t("Select SSH host number: ")).strip()
|
|
|
|
|
|
if not sel.isdigit() or not 1 <= int(sel) <= len(entrees):
|
|
|
|
|
|
print(t("Invalid selection!"))
|
|
|
|
|
|
return None
|
|
|
|
|
|
alias = entrees[int(sel) - 1][0]
|
|
|
|
|
|
# L'alias SEUL : ssh y lira l'utilisateur, le port et le ProxyJump.
|
|
|
|
|
|
return {"target": alias, "jump": ""}
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _pve_hostkey_missing(sortie):
|
|
|
|
|
|
"""La sortie de ssh dénonce-t-elle une clé d'hôte inconnue ou changée ?"""
|
|
|
|
|
|
bas = (sortie or "").lower()
|
|
|
|
|
|
return (
|
|
|
|
|
|
"host key verification failed" in bas
|
|
|
|
|
|
or "authenticity of host" in bas
|
|
|
|
|
|
or "no ed25519 host key is known" in bas
|
|
|
|
|
|
)
|
|
|
|
|
|
|
[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
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _pve_clean_output(sortie):
|
|
|
|
|
|
"""Les lignes de la sortie qui APPRENNENT quelque chose.
|
|
|
|
|
|
|
|
|
|
|
|
« Warning: Permanently added … to the list of known hosts » arrive sur
|
|
|
|
|
|
stderr à chaque connexion d'un hôte en UserKnownHostsFile=/dev/null.
|
|
|
|
|
|
Affichée comme preuve d'un échec, elle envoyait chercher du côté de la
|
|
|
|
|
|
clé d'hôte un problème qui n'avait rien à voir — rapporté.
|
|
|
|
|
|
"""
|
|
|
|
|
|
gardees = []
|
|
|
|
|
|
for ligne in (sortie or "").splitlines():
|
|
|
|
|
|
nue = ligne.strip()
|
|
|
|
|
|
if not nue or nue.startswith("Warning: Permanently added"):
|
|
|
|
|
|
continue
|
|
|
|
|
|
gardees.append(nue)
|
|
|
|
|
|
return gardees
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_ssh_alive(self, host):
|
|
|
|
|
|
"""(ssh passe-t-il ?, ce qu'il a dit) — sans rien exiger de la machine.
|
|
|
|
|
|
|
|
|
|
|
|
C'est la question qu'il fallait poser AVANT de conclure : une machine
|
|
|
|
|
|
qui répond mais n'a pas Proxmox n'est pas « injoignable », et les deux
|
|
|
|
|
|
pannes ne se corrigent pas du même côté."""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
code, out = pve.run(host, "true", timeout=20)
|
|
|
|
|
|
lignes = self._pve_clean_output(out)
|
|
|
|
|
|
return code == 0, (lignes[0] if lignes else t("no answer"))
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_install_hint(self, host):
|
|
|
|
|
|
"""La commande qui poserait Proxmox VE sur cette machine.
|
|
|
|
|
|
|
|
|
|
|
|
Le script du dépôt, poussé par le tube : il est autonome, donc
|
|
|
|
|
|
« bash -s » suffit et il n'y a rien à copier d'abord."""
|
|
|
|
|
|
return (
|
|
|
|
|
|
f"cat {self.PVE_INSTALL_SCRIPT} | "
|
|
|
|
|
|
f"ssh {host['target']} sudo bash -s"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
def _pve_add_hostkey(self, host):
|
|
|
|
|
|
"""Enregistre la clé d'hôte, après accord explicite.
|
|
|
|
|
|
|
|
|
|
|
|
ssh-keyscan et non « StrictHostKeyChecking=no » : la clé est écrite
|
|
|
|
|
|
UNE fois dans known_hosts, et toute substitution ultérieure sera
|
|
|
|
|
|
détectée. Désactiver la vérification l'aurait masquée pour toujours.
|
|
|
|
|
|
"""
|
|
|
|
|
|
cible = host["target"].split("@")[-1]
|
|
|
|
|
|
# Un alias de ~/.ssh/config n'est pas un nom de machine : ssh seul sait
|
|
|
|
|
|
# vers quoi il pointe.
|
|
|
|
|
|
resolu = self._ssh_resolve(host["target"])
|
|
|
|
|
|
nom = resolu.get("hostname") or cible
|
|
|
|
|
|
port = resolu.get("port") or host.get("port") or "22"
|
|
|
|
|
|
print(f"\n ⚠ {t('SSH does not know this host key yet.')}")
|
|
|
|
|
|
print(f" {t('Would record:')} ssh-keyscan -p {port} {nom}")
|
|
|
|
|
|
if not self._is_yes(input(f" {t('Record it?')} (o/N) : ")):
|
|
|
|
|
|
return False
|
|
|
|
|
|
try:
|
|
|
|
|
|
res = subprocess.run(
|
|
|
|
|
|
["ssh-keyscan", "-p", str(port), nom],
|
|
|
|
|
|
capture_output=True,
|
|
|
|
|
|
text=True,
|
|
|
|
|
|
timeout=30,
|
|
|
|
|
|
)
|
|
|
|
|
|
except (OSError, subprocess.SubprocessError) as exc:
|
|
|
|
|
|
print(f" ✗ ssh-keyscan : {exc}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
if res.returncode != 0 or not res.stdout.strip():
|
|
|
|
|
|
print(f" ✗ {t('No host key obtained.')}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
chemin = os.path.expanduser("~/.ssh/known_hosts")
|
|
|
|
|
|
os.makedirs(os.path.dirname(chemin), exist_ok=True)
|
|
|
|
|
|
with open(chemin, "a", encoding="utf-8") as fh:
|
[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
|
|
|
|
fh.write(
|
|
|
|
|
|
res.stdout if res.stdout.endswith("\n") else res.stdout + "\n"
|
|
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
lignes = len(res.stdout.strip().splitlines())
|
|
|
|
|
|
print(f" ✓ {lignes} {t('key(s) recorded in ~/.ssh/known_hosts')}")
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_confirm_host(self, host):
|
|
|
|
|
|
"""Vérifie que c'en est un, et le retient. Sinon, dit ce qu'il a vu.
|
|
|
|
|
|
|
|
|
|
|
|
« pveversion » est la preuve : une adresse saisie à la main peut être
|
|
|
|
|
|
n'importe quelle machine, et sans ce contrôle la première commande
|
|
|
|
|
|
« qm » échouerait sur un « command not found » qui n'explique rien.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
print(f"\n {t('Checking')} {host['target']}…")
|
|
|
|
|
|
code, out = pve.run(host, "pveversion", timeout=30)
|
|
|
|
|
|
version = pve.parse_pveversion(out)
|
|
|
|
|
|
if not version and self._pve_hostkey_missing(out):
|
|
|
|
|
|
# Première connexion : ssh refuse un hôte dont il n'a pas la clé.
|
|
|
|
|
|
# On ne DÉSACTIVE pas la vérification — un hyperviseur n'est pas
|
|
|
|
|
|
# une VM jetable — on propose de l'enregistrer, une fois.
|
|
|
|
|
|
if self._pve_add_hostkey(host):
|
|
|
|
|
|
code, out = pve.run(host, "pveversion", timeout=30)
|
|
|
|
|
|
version = pve.parse_pveversion(out)
|
|
|
|
|
|
if not version:
|
[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
|
|
|
|
# Un seul message confondait deux pannes : « ou il est
|
|
|
|
|
|
# injoignable » envoyait vérifier le réseau alors que la machine
|
|
|
|
|
|
# répondait, et la seule ligne montrée était l'avertissement de
|
|
|
|
|
|
# ssh sur la clé d'hôte. On demande donc à ssh s'il passe.
|
|
|
|
|
|
joignable, detail = self._pve_ssh_alive(host)
|
|
|
|
|
|
# Ce que « pveversion » a répondu, et non ce que la sonde a dit :
|
|
|
|
|
|
# « command not found » est LA preuve utile.
|
|
|
|
|
|
dit = self._pve_clean_output(out)
|
|
|
|
|
|
if joignable:
|
|
|
|
|
|
print(f" ✗ {t('Reachable, but Proxmox VE is not there:')}")
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" ssh {host['target']} : ok — pveversion : "
|
|
|
|
|
|
f"{dit[0] if dit else t('absent')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
print(f" → {t('Install it:')}")
|
|
|
|
|
|
print(f" {self._pve_install_hint(host)}")
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" → {t('Or redeploy the VM with the hypervisor profile.')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f" ✗ {t('SSH does not get through:')}")
|
|
|
|
|
|
print(f" {detail}")
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" → {t('Check the address, the SSH access and pveversion.')}"
|
|
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
return None
|
|
|
|
|
|
# « qm » exige les privilèges. La voie « VM QEMU locale » donne
|
|
|
|
|
|
# l'accès d'erplibre, pas de root : il faut donc sudo, et il faut le
|
|
|
|
|
|
# VÉRIFIER — un sudo qui réclame un mot de passe bloquerait chaque
|
|
|
|
|
|
# commande du menu sur une invite que personne ne voit.
|
|
|
|
|
|
prefixe = ""
|
|
|
|
|
|
_c, qui = pve.run(host, "id -u", timeout=20)
|
|
|
|
|
|
if qui.strip() != "0":
|
|
|
|
|
|
code, _o = pve.run(host, "sudo -n true", timeout=20)
|
|
|
|
|
|
if code:
|
[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
|
|
|
|
print(
|
|
|
|
|
|
f" ✗ {t('qm needs root: no root, and sudo asks for a password.')}"
|
|
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
print(f" → {t('Connect as root@, or allow NOPASSWD sudo.')}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
prefixe = "sudo "
|
|
|
|
|
|
print(f" ✓ sudo")
|
|
|
|
|
|
host = dict(host, version=version, sudo=prefixe)
|
|
|
|
|
|
print(f" ✓ Proxmox VE {version}")
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
# Le noyau DÉCIDE de ce qui marche : sans le noyau Proxmox, ni module
|
|
|
|
|
|
# bridge ni table NAT — donc aucun pont à créer et aucune VM à
|
|
|
|
|
|
# démarrer. Vécu sur l'hôte d'essai, où ifupdown2 répondait
|
|
|
|
|
|
# « Another instance of this program is already running » au lieu de
|
|
|
|
|
|
# « Operation not supported ». On le dit ici, une fois, plutôt que de
|
|
|
|
|
|
# laisser chercher.
|
|
|
|
|
|
noyau = pve.parse_kernel(out)
|
|
|
|
|
|
if noyau and "-pve" not in noyau:
|
|
|
|
|
|
print(f" ⚠ {t('Still on the distribution kernel:')} {noyau}")
|
|
|
|
|
|
print(f" → {t('Reboot the host: no bridge, no NAT until then.')}")
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
self._pve_remember_host(host)
|
|
|
|
|
|
return host
|
|
|
|
|
|
|
|
|
|
|
|
# -- Exécution sur l'hôte ------------------------------------------ #
|
|
|
|
|
|
def _pve_show(self, remote, timeout=120, quiet=False):
|
|
|
|
|
|
"""Exécute `remote` sur l'hôte Proxmox et montre ce qui a été lancé.
|
|
|
|
|
|
|
|
|
|
|
|
La commande est AFFICHÉE avant sa sortie : c'est ce qui rend chaque
|
|
|
|
|
|
étape rejouable à la main, et c'est ainsi que les pannes de ce module
|
|
|
|
|
|
ont été diagnostiquées.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
host = self._pve_host()
|
|
|
|
|
|
if not host:
|
|
|
|
|
|
return 255, ""
|
|
|
|
|
|
if not quiet:
|
[ADD] proxmox : l'écran remet pmxcfs debout lui-même
Le conseil « rejouer install_proxmox.sh sur l'hôte » ne pouvait PAS marcher :
la VM clone le dépôt distant, donc sa copie du script est celle du distant —
tant que le correctif n'y est pas, celle qui ne corrige rien. Trois hôtes de
suite sont tombés dessus, avec le même message inutile. L'écran répare donc :
gel de cloud-init, réécriture de /etc/hosts, relance des unités, constat du
montage — le pendant exact de l'offre de créer un pont.
Écrit, puis ATTAQUÉ par trois lentilles sur le code réel. Ce qu'elles ont
mesuré valait la peine.
/etc/hosts se réécrivait en DEUX écritures — « sed -i » puis « printf >> » —
alors que la docstring promettait l'inverse. Sed refusé et ajout réussi, la
ligne 127.0.1.1 survivait EN PREMIER et la nôtre s'ajoutait une fois par
tentative ; sed réussi et ajout refusé, l'hôte perdait l'entrée de son nom, et
chaque sudo y attendait ensuite le résolveur. C'est maintenant un fichier
complet bâti dans un temporaire, VÉRIFIÉ, puis recopié — « cat > » et non
« mv », qui remplacerait l'inode et perdrait mode et propriétaire.
Le contrôle final s'en remettait à « getent hosts », qui réussit via mDNS même
quand rien n'a été écrit — et acceptait les fe80:: que notre propre code
rejette. Il relit désormais ce qui a été écrit.
awk remplace sed pour filtrer : « print » émet un saut de ligne, donc un
/etc/hosts non terminé par un — cloud-init n'en met pas — est normalisé. Sans
ça notre ligne se collait à la précédente et le nom du nœud partait sur
l'adresse d'une autre machine.
Trois autres, du même acabit. Les dépendants de pmxcfs sont relancés eux
aussi : actifs pendant la panne, ils échouaient sur ipcc_send_rec, et les
laisser donnait une GUI en « communication failure » juste après notre ✓. Un
silence du lien n'est plus lu comme une absence de montage. Et l'adresse n'est
mise en cause que si pve-cluster a réellement démarré.
Les tests exécutent les commandes au lieu de les relire, bouchons capables
d'ÉCHOUER : écriture refusée, fichier sans saut de ligne final, tabulations,
start qui rate, montage qui disparaît pendant la reconfirmation. Prouvé par
mutation — trois HOSTS-KO changés en HOSTS-OK font rougir le test.
--- EN ---
The advice "replay install_proxmox.sh on the host" could NOT work: the VM
clones the remote, so its copy of the script is the remote's — while the fix
is not there, the one that fixes nothing. Three hosts in a row hit it with the
same useless message. So the screen repairs: freeze cloud-init, rewrite
/etc/hosts, restart the units, verify the mount — the exact counterpart of the
offer to create a bridge.
Written, then ATTACKED by three lenses on the real code. What they measured
was worth it.
/etc/hosts was rewritten in TWO writes — "sed -i" then "printf >>" — while the
docstring promised the opposite. Sed refused and append succeeded: the
127.0.1.1 line survived FIRST and ours was added once per attempt; sed
succeeded and append refused: the host lost its own name entry, and every sudo
then waited on the resolver. It is now a complete file built in a temporary,
VERIFIED, then copied over — "cat >" not "mv", which would replace the inode
and lose mode and owner.
The final check relied on "getent hosts", which succeeds via mDNS even when
nothing was written — and accepted the fe80:: our own code rejects. It now
re-reads what was written.
awk replaces sed for filtering: "print" emits a newline, so an /etc/hosts with
no final one — cloud-init omits it — gets normalised. Without that our line
glued onto the previous one and the node's name pointed at another machine's
address.
Three more of the same kind. pmxcfs's dependents are restarted too: active
throughout the outage, they failed on ipcc_send_rec, and leaving them gave a
GUI in "communication failure" right after our ✓. A silent link is no longer
read as a missing mount. And the address is only blamed if pve-cluster
actually started.
The tests execute the commands instead of reading them, with stubs able to
FAIL: refused write, file with no final newline, tabs, a start that fails, a
mount that vanishes during reconfirmation. Proven by mutation — three
HOSTS-KO turned into HOSTS-OK make the test go red.
Assisted-by: Claude Opus 5
(cherry picked from commit d4f9358c6cb562029cc2ca9eb478d80c6a0a19a4)
2026-08-26 03:19:58 -04:00
|
|
|
|
# La forme RÉELLEMENT envoyée : enrobage sudo, rebond et port
|
|
|
|
|
|
# compris. Sans « -J », la ligne copiée rendait « no route to
|
|
|
|
|
|
# host » — et c'est justement quand une étape échoue au milieu
|
|
|
|
|
|
# d'une réparation qu'on a besoin de la rejouer à la main.
|
|
|
|
|
|
argv = pve.ssh_argv(
|
|
|
|
|
|
host, pve.wrap_privilege(remote, host.get("sudo") or "")
|
|
|
|
|
|
)
|
|
|
|
|
|
print(
|
|
|
|
|
|
f"\n{t('Will execute:')} "
|
|
|
|
|
|
+ " ".join(shlex.quote(a) for a in argv)
|
|
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
code, out = pve.run(host, remote, timeout)
|
|
|
|
|
|
if out.strip() and not quiet:
|
|
|
|
|
|
print(out.rstrip())
|
|
|
|
|
|
if code and not quiet:
|
|
|
|
|
|
print(f" ⚠ {t('exit code')} {code}")
|
|
|
|
|
|
return code, out
|
|
|
|
|
|
|
[ADD] proxmox deploy : accélération 3D, bibliothèques sondées et posées
A Proxmox VM always used the serial console as its display, so nothing inside
it could render. The form offers 3D: the VM gets « --vga virtio-gl » and the
account joins the render and video groups in the guest, which « qm set »
cannot write; without them GL falls back to software while VIRGL reports
success. The box is offered only where the host has a render node, VIRGL, GL
and EGL, Proxmox refusing to start the VM otherwise once its disk is written.
What is missing is named with its package, and a button installs it, then
reads the host again. Covered by test/test_proxmox_form.py.
--- FR ---
Une VM Proxmox prenait toujours la console série pour affichage : rien ne
pouvait y rendre. Le formulaire offre la 3D : la VM reçoit « --vga virtio-gl »
et le compte entre dans les groupes render et video dans l'invité, ce que
« qm set » ne sait pas écrire ; sans eux, GL retombe en logiciel alors que
VIRGL annonce un succès. La case n'est offerte que si l'hôte a un nœud de
rendu, VIRGL, GL et EGL, Proxmox refusant sinon de démarrer la VM une fois son
disque écrit. Ce qui manque est nommé avec son paquet, et un bouton le pose
puis relit l'hôte. Couvert par test/test_proxmox_form.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:20 -04:00
|
|
|
|
# Ce que « --vga virtio-gl » exige de l'hôte : le chemin qui le prouve,
|
|
|
|
|
|
# et le nom qui sert à le poser. Les noms sont ceux des paquets, car
|
|
|
|
|
|
# c'est ce qu'un opérateur tape ; « noeud » n'en est pas un, le nœud de
|
|
|
|
|
|
# rendu venant du matériel ou d'un GPU transmis.
|
|
|
|
|
|
_PVE_GPU_PIECES = (
|
|
|
|
|
|
("/dev/dri/renderD*", "noeud"),
|
|
|
|
|
|
("/usr/lib/*/libvirglrenderer.so.*", "libvirglrenderer1"),
|
|
|
|
|
|
("/usr/lib/*/libGL.so.1", "libgl1"),
|
|
|
|
|
|
("/usr/lib/*/libEGL.so.1", "libegl1"),
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_gpu_dispo(self):
|
|
|
|
|
|
"""Ce qui MANQUE à l'hôte Proxmox pour donner de la 3D à ses invités.
|
|
|
|
|
|
|
|
|
|
|
|
Rend (possible, manque) : « manque » nomme les pièces absentes et
|
|
|
|
|
|
vaut "" quand tout est là. Une case qui disparaît sans un mot ne se
|
|
|
|
|
|
devine pas — le formulaire s'en sert pour DIRE pourquoi la 3D n'est
|
|
|
|
|
|
pas offerte, et quoi installer.
|
|
|
|
|
|
|
|
|
|
|
|
Une sonde qui n'aboutit pas rend (False, "") : on ne promet rien, et
|
|
|
|
|
|
on n'accuse rien non plus. Le jeton « FIN » distingue une sonde qui a
|
|
|
|
|
|
tout trouvé — donc muette — d'une sonde qui n'a pas tourné.
|
|
|
|
|
|
|
|
|
|
|
|
Quatre pièces, et il les faut TOUTES. Un NŒUD DE RENDU
|
|
|
|
|
|
(« /dev/dri/renderD* ») : un hôte sans GPU, ou lui-même virtualisé
|
|
|
|
|
|
sans GPU transmis, n'en expose aucun. Puis les trois bibliothèques
|
|
|
|
|
|
que Proxmox charge pour « --vga virtio-gl » : VIRGL, GL et EGL. Il
|
|
|
|
|
|
les réclame nommément — « missing libraries for 'virtio-gl'
|
|
|
|
|
|
detected! Please install 'libgl1' and 'libegl1' » — et refuse de
|
|
|
|
|
|
démarrer la machine, une fois son disque écrit et sa configuration
|
|
|
|
|
|
posée. La case promettrait alors une accélération que rien ne
|
|
|
|
|
|
fournit, et le déploiement échouerait à la dernière étape.
|
|
|
|
|
|
|
|
|
|
|
|
Un hôte peut en porter une sans l'autre : VIRGL et GL viennent avec
|
|
|
|
|
|
d'autres paquets, EGL non.
|
|
|
|
|
|
"""
|
|
|
|
|
|
sonde = "; ".join(
|
|
|
|
|
|
f"ls {chemin} >/dev/null 2>&1 || echo {jeton}"
|
|
|
|
|
|
for chemin, jeton in self._PVE_GPU_PIECES
|
|
|
|
|
|
)
|
|
|
|
|
|
code, sortie = self._pve_show(f"{sonde}; echo FIN", quiet=True)
|
|
|
|
|
|
dites = [l.strip() for l in (sortie or "").splitlines() if l.strip()]
|
|
|
|
|
|
if code or "FIN" not in dites:
|
|
|
|
|
|
return False, ""
|
|
|
|
|
|
manque = [
|
|
|
|
|
|
jeton for _c, jeton in self._PVE_GPU_PIECES if jeton in dites
|
|
|
|
|
|
]
|
|
|
|
|
|
return (not manque), " ".join(manque)
|
|
|
|
|
|
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
def _pve_vms(self):
|
|
|
|
|
|
"""[{vmid, name, status, …}] des VM de l'hôte, ou []."""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
code, out = self._pve_show("qm list", quiet=True)
|
|
|
|
|
|
return pve.parse_qm_list(out) if code == 0 else []
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_pick_vm(self, titre="", multiple=False):
|
|
|
|
|
|
"""Choisit une VM de l'hôte (numéro de la liste, jamais le VMID à
|
|
|
|
|
|
retaper). Renvoie un dict, une liste si `multiple`, ou None."""
|
|
|
|
|
|
vms = self._pve_vms()
|
|
|
|
|
|
if not vms:
|
|
|
|
|
|
print(f"\n{t('No VM on this Proxmox host.')}")
|
|
|
|
|
|
return [] if multiple else None
|
|
|
|
|
|
print(f"\n{titre or t('VMs on this host:')}")
|
|
|
|
|
|
for i, vm in enumerate(vms, 1):
|
[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
|
|
|
|
print(f" [{i}] {vm['vmid']:<6} {vm['name']:<28} {vm['status']}")
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
if multiple:
|
|
|
|
|
|
print(f" [all] {t('select all')}")
|
|
|
|
|
|
brut = input(t("Selection (number): ")).strip()
|
|
|
|
|
|
if multiple:
|
|
|
|
|
|
if brut.lower() in ("all", "*"):
|
|
|
|
|
|
return vms
|
|
|
|
|
|
choisis = []
|
|
|
|
|
|
for jeton in re.split(r"[\s,]+", brut):
|
|
|
|
|
|
if jeton.isdigit() and 1 <= int(jeton) <= len(vms):
|
|
|
|
|
|
choisis.append(vms[int(jeton) - 1])
|
|
|
|
|
|
return choisis
|
|
|
|
|
|
if brut.isdigit() and 1 <= int(brut) <= len(vms):
|
|
|
|
|
|
return vms[int(brut) - 1]
|
|
|
|
|
|
print(t("Invalid selection!"))
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
# -- Les commandes du menu ----------------------------------------- #
|
|
|
|
|
|
def _pve_list(self):
|
|
|
|
|
|
"""« qm list », mis en tableau avec le total."""
|
|
|
|
|
|
vms = self._pve_vms()
|
|
|
|
|
|
if not vms:
|
|
|
|
|
|
print(f"\n{t('No VM on this Proxmox host.')}")
|
|
|
|
|
|
return
|
|
|
|
|
|
print(
|
|
|
|
|
|
f"\n{'VMID':<7} {'Nom':<30} {'État':<10} {'RAM (Mo)':>9}"
|
|
|
|
|
|
f" {'Disque':>10}"
|
|
|
|
|
|
)
|
|
|
|
|
|
print("─" * 70)
|
|
|
|
|
|
for vm in vms:
|
|
|
|
|
|
print(
|
|
|
|
|
|
f"{vm['vmid']:<7} {vm['name'][:30]:<30} {vm['status']:<10}"
|
|
|
|
|
|
f" {vm['mem']:>9} {vm['disk']:>10}"
|
|
|
|
|
|
)
|
|
|
|
|
|
actives = sum(1 for v in vms if v["status"] == "running")
|
|
|
|
|
|
print(f"\n {len(vms)} VM, {actives} {t('running')}")
|
[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
|
|
|
|
# Le pendant du sous-menu de QEMU/KVM : la liste est le bon endroit
|
|
|
|
|
|
# pour agir sur ce qu'on vient de lire.
|
|
|
|
|
|
print(f"\n [1] {t('Change the state of one or more VMs')}")
|
|
|
|
|
|
if input(t("Choice (blank = back): ")).strip() == "1":
|
|
|
|
|
|
self._pve_change_state(vms)
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_change_state(self, vms=None):
|
|
|
|
|
|
"""Démarre ou éteint des VM de l'hôte, avec double validation.
|
|
|
|
|
|
|
|
|
|
|
|
« shutdown » et non « stop » : on demande à l'invité de s'arrêter, ce
|
|
|
|
|
|
qui laisse Odoo fermer ses connexions PostgreSQL. « stop » coupe le
|
|
|
|
|
|
courant — il est offert en second, nommé pour ce qu'il est.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
vms = vms if vms is not None else self._pve_vms()
|
|
|
|
|
|
if not vms:
|
|
|
|
|
|
print(f"\n{t('No VM on this Proxmox host.')}")
|
|
|
|
|
|
return
|
|
|
|
|
|
print(f"\n{t('Available VMs:')}")
|
|
|
|
|
|
for i, vm in enumerate(vms, 1):
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" [{i}] {vm['vmid']:<7} {vm['name'][:34]:<34} {vm['status']}"
|
|
|
|
|
|
)
|
|
|
|
|
|
print(f" [all] {t('select all')}")
|
|
|
|
|
|
brut = input(t("Selection (numbers, or 'all'): ")).strip().lower()
|
|
|
|
|
|
if not brut:
|
|
|
|
|
|
print(t("Nothing selected."))
|
|
|
|
|
|
return
|
|
|
|
|
|
if brut in ("all", "*"):
|
|
|
|
|
|
choisies = list(vms)
|
|
|
|
|
|
else:
|
[FIX] proxmox : six défauts trouvés par un audit, pas à l'usage
Trois autres chemins menaient au 🗑 sur un seul incident, et « effacée » gèle
la ligne pour de bon. Un « virsh list » en échec condamnait TOUT le parc
local. Un statut Proxmox hors des trois attendus — prelaunch, suspended,
internal-error — passait pour une disparition. Et le code de sortie de la
suite distante est celui de son DERNIER maillon : un pvesh en panne se lisait
« l'hôte a répondu sans elle ». Ce qui prouve une réponse, c'est désormais une
liste de ressources analysable.
Le plan annonçait « 25G » quand « qm resize » recevait 20 : la marge
d'ERPLibre se perdait en route, la VM naissait trop petite. « Changer l'état »
choisissait par NOM, or seul le VMID est unique sur un hôte — cocher une VM
en éteignait deux homonymes. L'entrée 13 volait son alias à une VM locale du
même nom. Enfin le déploiement par QUESTIONS avait vieilli seul : il partage
maintenant l'épilogue de l'écran, donc le guide, l'alias protégé, les colonnes
vivantes et le sommaire.
--- EN ---
Three more paths led to 🗑 on a single incident, and "deleted" freezes the row
for good. One failing "virsh list" condemned the WHOLE local fleet. A Proxmox
status outside the three expected ones — prelaunch, suspended, internal-error
— passed for a disappearance. And a remote pipeline's exit code is its LAST
link's: a broken pvesh read as "the host answered without it". Proof of an
answer is now a parsable resource list.
The plan announced "25G" while "qm resize" got 20: ERPLibre's margin was lost
on the way and the VM was born too small. "Change state" selected by NAME,
yet only the VMID is unique on a host — ticking one VM shut down two
namesakes. Menu entry 13 stole its alias from a local VM of the same name.
Finally the QUESTION-driven deployment had aged alone: it now shares the
screen's epilogue — guide, protected alias, live columns and summary.
Assisted-by: Claude Opus 5
2026-08-24 14:15:03 -04:00
|
|
|
|
# Par RANG, jamais par nom : deux VM du même hôte peuvent
|
|
|
|
|
|
# porter le même nom (seul le VMID est unique sur Proxmox), et
|
|
|
|
|
|
# cocher l'une éteignait les deux.
|
|
|
|
|
|
rangs = self._parse_index_selection(
|
|
|
|
|
|
brut, [str(i) for i in range(1, len(vms) + 1)]
|
|
|
|
|
|
)
|
|
|
|
|
|
voulus = {int(r) for r in rangs if str(r).isdigit()}
|
|
|
|
|
|
choisies = [vm for i, vm in enumerate(vms, 1) if i in voulus]
|
[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
|
|
|
|
if not choisies:
|
|
|
|
|
|
print(t("Nothing selected."))
|
|
|
|
|
|
return
|
|
|
|
|
|
print(
|
|
|
|
|
|
f"\n [1] {t('start')} [2] {t('shutdown (clean)')}"
|
|
|
|
|
|
f" [3] {t('stop (pulls the plug)')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
geste = input(t("Choice: ")).strip()
|
|
|
|
|
|
verbe = {"1": "start", "2": "shutdown", "3": "stop"}.get(geste)
|
|
|
|
|
|
if not verbe:
|
|
|
|
|
|
print(t("Cancelled."))
|
|
|
|
|
|
return
|
|
|
|
|
|
print(
|
|
|
|
|
|
f"\n {verbe} : "
|
|
|
|
|
|
+ ", ".join(f"{vm['name']} ({vm['vmid']})" for vm in choisies)
|
|
|
|
|
|
)
|
|
|
|
|
|
if not self._is_yes(input(t("Confirm? (y/N): "))):
|
|
|
|
|
|
print(t("Cancelled."))
|
|
|
|
|
|
return
|
|
|
|
|
|
for vm in choisies:
|
|
|
|
|
|
code, _o = self._pve_show(f"qm {verbe} {vm['vmid']}", timeout=300)
|
|
|
|
|
|
marque = "✓" if code == 0 else "✗"
|
|
|
|
|
|
print(f" {marque} {vm['name']}")
|
|
|
|
|
|
# L'état APRÈS : c'est la seule preuve que le geste a porté.
|
|
|
|
|
|
_c, out = self._pve_show("qm list", quiet=True)
|
|
|
|
|
|
for vm in pve.parse_qm_list(out):
|
|
|
|
|
|
if any(vm["vmid"] == c["vmid"] for c in choisies):
|
|
|
|
|
|
print(f" {vm['name']:<34} {vm['status']}")
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
|
|
|
|
|
|
def _pve_vm_ip(self):
|
|
|
|
|
|
"""Adresse d'une VM, par l'agent invité.
|
|
|
|
|
|
|
|
|
|
|
|
Sans agent, Proxmox ne connaît PAS l'adresse de ses invités : il ne la
|
|
|
|
|
|
distribue pas lui-même. Le dire vaut mieux qu'afficher « rien ».
|
|
|
|
|
|
"""
|
|
|
|
|
|
vm = self._pve_pick_vm()
|
|
|
|
|
|
if not vm:
|
|
|
|
|
|
return
|
|
|
|
|
|
# _pve_guest_ip et non l'agent seul : il enchaîne agent PUIS voisinage
|
|
|
|
|
|
# de l'hôte. L'image cloud Debian n'embarque pas qemu-guest-agent, et
|
|
|
|
|
|
# cette entrée du menu répondait « aucune adresse » alors que « ip
|
|
|
|
|
|
# neigh » la connaissait — deux chemins pour la même question, dont un
|
|
|
|
|
|
# seul savait répondre.
|
|
|
|
|
|
ip = self._pve_guest_ip(vm["vmid"], attente=20)
|
|
|
|
|
|
if ip:
|
|
|
|
|
|
print(f"\n {vm['name']} : {ip}")
|
|
|
|
|
|
print(f" ssh erplibre@{ip}")
|
|
|
|
|
|
return
|
|
|
|
|
|
print(f"\n ⚠ {t('No address for this VM.')}")
|
|
|
|
|
|
print(f" → {t('Is qemu-guest-agent installed and the VM started?')}")
|
|
|
|
|
|
print(f" → {t('A static address is visible right after creation.')}")
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_console(self):
|
|
|
|
|
|
"""Console série d'une VM. Demande un terminal : on passe donc par
|
|
|
|
|
|
l'exécuteur du dépôt, qui en a un."""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
vm = self._pve_pick_vm()
|
|
|
|
|
|
if not vm:
|
|
|
|
|
|
return
|
|
|
|
|
|
host = self._pve_host()
|
|
|
|
|
|
if not host:
|
|
|
|
|
|
return
|
|
|
|
|
|
cmd = " ".join(
|
|
|
|
|
|
shlex.quote(a)
|
|
|
|
|
|
for a in pve.ssh_argv(
|
|
|
|
|
|
host,
|
|
|
|
|
|
pve.wrap_privilege(
|
|
|
|
|
|
pve.console_cmd(vm["vmid"]), host.get("sudo") or ""
|
|
|
|
|
|
),
|
|
|
|
|
|
tty=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
print(f"\n {t('Ctrl+O to quit the serial console.')}")
|
|
|
|
|
|
print(f"\n{t('Will execute:')} {cmd}")
|
|
|
|
|
|
self.execute.exec_command_live(cmd, source_erplibre=False)
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_resize(self):
|
|
|
|
|
|
"""Agrandit un disque. Proxmox REFUSE de rétrécir : on le dit avant."""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
vm = self._pve_pick_vm()
|
|
|
|
|
|
if not vm:
|
|
|
|
|
|
return
|
|
|
|
|
|
print(f"\n ⚠ {t('Proxmox can only GROW a disk, never shrink it.')}")
|
[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
|
|
|
|
taille = input(t("Size (+10G to add, 40G for a target): ")).strip()
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
if not re.match(r"^\+?\d+[MGT]$", taille):
|
|
|
|
|
|
print(t("Invalid selection!"))
|
|
|
|
|
|
return
|
|
|
|
|
|
self._pve_show(pve.resize_cmd(vm["vmid"], taille))
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_delete(self):
|
|
|
|
|
|
"""Efface des VM, avec DOUBLE validation — « --purge » emporte les
|
|
|
|
|
|
disques et les sauvegardes, il n'y a pas de retour."""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
vms = self._pve_pick_vm(multiple=True)
|
|
|
|
|
|
if not vms:
|
|
|
|
|
|
return
|
|
|
|
|
|
noms = ", ".join(f"{v['vmid']} ({v['name']})" for v in vms)
|
|
|
|
|
|
print(f"\n ⚠ {t('This also destroys their disks and backups.')}")
|
|
|
|
|
|
if not self._is_yes(input(f"{t('Apply:')} {noms} ? (o/N) : ")):
|
|
|
|
|
|
print(t("Cancelled."))
|
|
|
|
|
|
return
|
|
|
|
|
|
if not self._is_yes(input(t("Confirm for real? (y/N): "))):
|
|
|
|
|
|
print(t("Cancelled."))
|
|
|
|
|
|
return
|
|
|
|
|
|
for vm in vms:
|
|
|
|
|
|
for cmd in pve.destroy_cmds(vm["vmid"]):
|
|
|
|
|
|
self._pve_show(cmd, timeout=300)
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_cleanup(self):
|
|
|
|
|
|
"""Volumes de disque qu'aucune VM ne réclame plus.
|
|
|
|
|
|
|
|
|
|
|
|
Proxmox ne les efface pas de lui-même : une création interrompue ou un
|
|
|
|
|
|
« destroy » sans « --purge » en laisse. On les liste et on demande.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
code, out = self._pve_show(pve.orphan_disks_cmd(), quiet=True)
|
|
|
|
|
|
if code:
|
|
|
|
|
|
print(f"\n ⚠ {t('exit code')} {code}")
|
|
|
|
|
|
return
|
|
|
|
|
|
vmids = [v["vmid"] for v in self._pve_vms()]
|
|
|
|
|
|
orphelins = pve.parse_orphans(out, vmids)
|
|
|
|
|
|
if not orphelins:
|
|
|
|
|
|
print(f"\n ✓ {t('Nothing orphaned.')}")
|
|
|
|
|
|
return
|
|
|
|
|
|
total = sum(t2 for _v, t2 in orphelins)
|
|
|
|
|
|
print(f"\n{t('Orphan disks:')}")
|
|
|
|
|
|
for volid, taille in orphelins:
|
|
|
|
|
|
print(f" {volid:<48} {taille / (1 << 30):>8.1f} Go")
|
|
|
|
|
|
print(f" {t('Total:')} {total / (1 << 30):.1f} Go")
|
|
|
|
|
|
if not self._is_yes(input(f"{t('Free them?')} (o/N) : ")):
|
|
|
|
|
|
print(t("Cancelled."))
|
|
|
|
|
|
return
|
|
|
|
|
|
for volid, _taille in orphelins:
|
|
|
|
|
|
self._pve_show(f"pvesm free {shlex.quote(volid)}", timeout=300)
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_guest_ip(self, vmid, attente=120):
|
|
|
|
|
|
"""Adresse d'une VM Proxmox : agent invité, sinon voisinage de l'hôte.
|
|
|
|
|
|
|
|
|
|
|
|
Deux voies parce qu'aucune ne suffit seule. L'agent est le plus sûr,
|
|
|
|
|
|
mais l'image cloud Debian ne l'embarque pas. Le voisinage (« ip neigh »
|
|
|
|
|
|
sur l'hôte) marche dès que la VM a émis un paquet — un bail DHCP suffit
|
|
|
|
|
|
— et ne demande RIEN à l'invité.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
fin = time.time() + attente
|
|
|
|
|
|
mac = ""
|
|
|
|
|
|
while True:
|
|
|
|
|
|
code, out = self._pve_show(
|
|
|
|
|
|
pve.guest_ip_cmd(vmid), timeout=30, quiet=True
|
|
|
|
|
|
)
|
|
|
|
|
|
ips = pve.parse_guest_ips(out) if code == 0 else []
|
|
|
|
|
|
if ips:
|
|
|
|
|
|
return ips[0]
|
|
|
|
|
|
if not mac:
|
|
|
|
|
|
_c, cfg = self._pve_show(
|
|
|
|
|
|
f"qm config {vmid}", timeout=30, quiet=True
|
|
|
|
|
|
)
|
|
|
|
|
|
mac = pve.mac_from_config(cfg)
|
|
|
|
|
|
if mac:
|
|
|
|
|
|
_c, neigh = self._pve_show(
|
|
|
|
|
|
"ip -4 neigh show", timeout=30, quiet=True
|
|
|
|
|
|
)
|
|
|
|
|
|
ip = pve.ip_from_neigh(neigh, mac)
|
|
|
|
|
|
if ip:
|
|
|
|
|
|
return ip
|
|
|
|
|
|
if time.time() >= fin:
|
|
|
|
|
|
return ""
|
|
|
|
|
|
time.sleep(5)
|
|
|
|
|
|
|
2026-09-16 00:42:31 -04:00
|
|
|
|
def _pve_user_data(self, mod, distro, nom, cle_locale):
|
|
|
|
|
|
"""Le user-data du DÉPÔT pour une VM Proxmox, ou "".
|
|
|
|
|
|
|
|
|
|
|
|
Le même que le chemin qemu envoie : un bloc « users: » explicite, avec
|
|
|
|
|
|
le nom du compte, son shell, son sudo et ses clés. « --ciuser » et
|
|
|
|
|
|
« --sshkeys » de Proxmox s'en remettent au compte par DÉFAUT de
|
|
|
|
|
|
l'image, et une image qui en déclare un autre les ignore — mesuré sur
|
|
|
|
|
|
NixOS, dont le cloud.cfg nomme « nixos » : la VM démarrait avec ce
|
|
|
|
|
|
compte-là, sans la clé, donc injoignable.
|
|
|
|
|
|
|
|
|
|
|
|
Rend "" quand la clé publique est illisible : mieux vaut retomber sur
|
|
|
|
|
|
la forme d'avant, qui pose au moins un compte, que d'écrire un
|
|
|
|
|
|
user-data sans aucun moyen d'entrer.
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
with open(os.path.expanduser(cle_locale), encoding="utf-8") as fh:
|
|
|
|
|
|
cle = fh.read().strip()
|
|
|
|
|
|
except OSError as exc:
|
|
|
|
|
|
print(f" ⚠ {t('SSH key unreadable:')} {exc}")
|
|
|
|
|
|
return ""
|
|
|
|
|
|
if not cle:
|
|
|
|
|
|
return ""
|
|
|
|
|
|
args = mod.build_parser().parse_args(
|
|
|
|
|
|
["--distro", distro, "--hostname", nom, "--user", "erplibre"]
|
|
|
|
|
|
)
|
|
|
|
|
|
return mod.build_cloud_config(args, None, [cle])
|
|
|
|
|
|
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
def _pve_push_key(self, chemin_local):
|
|
|
|
|
|
"""Recopie la clé publique SUR l'hôte : « qm set --sshkeys » attend un
|
|
|
|
|
|
FICHIER là-bas, pas une clé en ligne."""
|
|
|
|
|
|
try:
|
[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
|
|
|
|
with open(
|
|
|
|
|
|
os.path.expanduser(chemin_local), encoding="utf-8"
|
|
|
|
|
|
) as fh:
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
cle = fh.read().strip()
|
|
|
|
|
|
except OSError as exc:
|
|
|
|
|
|
print(f" ⚠ {t('SSH key unreadable:')} {exc}")
|
|
|
|
|
|
return ""
|
|
|
|
|
|
distant = "/root/.ssh/erplibre-deploy.pub"
|
|
|
|
|
|
code, _out = self._pve_show(
|
|
|
|
|
|
"mkdir -p /root/.ssh && printf '%s\\n' "
|
|
|
|
|
|
f"{shlex.quote(cle)} > {distant}",
|
|
|
|
|
|
quiet=True,
|
|
|
|
|
|
)
|
|
|
|
|
|
return distant if code == 0 else ""
|
|
|
|
|
|
|
[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
|
|
|
|
def _pve_uplink(self):
|
|
|
|
|
|
"""Interface qui porte la route par défaut, ou '' — la sortie du NAT.
|
|
|
|
|
|
|
|
|
|
|
|
Sans elle, le pont interne existe mais ses VM ne voient pas Internet.
|
|
|
|
|
|
"""
|
|
|
|
|
|
_c, sortie = self._pve_show("ip -o -4 route show default", quiet=True)
|
|
|
|
|
|
parts = (sortie or "").split()
|
|
|
|
|
|
return parts[parts.index("dev") + 1] if "dev" in parts else ""
|
|
|
|
|
|
|
[ADD] proxmox : l'écran remet pmxcfs debout lui-même
Le conseil « rejouer install_proxmox.sh sur l'hôte » ne pouvait PAS marcher :
la VM clone le dépôt distant, donc sa copie du script est celle du distant —
tant que le correctif n'y est pas, celle qui ne corrige rien. Trois hôtes de
suite sont tombés dessus, avec le même message inutile. L'écran répare donc :
gel de cloud-init, réécriture de /etc/hosts, relance des unités, constat du
montage — le pendant exact de l'offre de créer un pont.
Écrit, puis ATTAQUÉ par trois lentilles sur le code réel. Ce qu'elles ont
mesuré valait la peine.
/etc/hosts se réécrivait en DEUX écritures — « sed -i » puis « printf >> » —
alors que la docstring promettait l'inverse. Sed refusé et ajout réussi, la
ligne 127.0.1.1 survivait EN PREMIER et la nôtre s'ajoutait une fois par
tentative ; sed réussi et ajout refusé, l'hôte perdait l'entrée de son nom, et
chaque sudo y attendait ensuite le résolveur. C'est maintenant un fichier
complet bâti dans un temporaire, VÉRIFIÉ, puis recopié — « cat > » et non
« mv », qui remplacerait l'inode et perdrait mode et propriétaire.
Le contrôle final s'en remettait à « getent hosts », qui réussit via mDNS même
quand rien n'a été écrit — et acceptait les fe80:: que notre propre code
rejette. Il relit désormais ce qui a été écrit.
awk remplace sed pour filtrer : « print » émet un saut de ligne, donc un
/etc/hosts non terminé par un — cloud-init n'en met pas — est normalisé. Sans
ça notre ligne se collait à la précédente et le nom du nœud partait sur
l'adresse d'une autre machine.
Trois autres, du même acabit. Les dépendants de pmxcfs sont relancés eux
aussi : actifs pendant la panne, ils échouaient sur ipcc_send_rec, et les
laisser donnait une GUI en « communication failure » juste après notre ✓. Un
silence du lien n'est plus lu comme une absence de montage. Et l'adresse n'est
mise en cause que si pve-cluster a réellement démarré.
Les tests exécutent les commandes au lieu de les relire, bouchons capables
d'ÉCHOUER : écriture refusée, fichier sans saut de ligne final, tabulations,
start qui rate, montage qui disparaît pendant la reconfirmation. Prouvé par
mutation — trois HOSTS-KO changés en HOSTS-OK font rougir le test.
--- EN ---
The advice "replay install_proxmox.sh on the host" could NOT work: the VM
clones the remote, so its copy of the script is the remote's — while the fix
is not there, the one that fixes nothing. Three hosts in a row hit it with the
same useless message. So the screen repairs: freeze cloud-init, rewrite
/etc/hosts, restart the units, verify the mount — the exact counterpart of the
offer to create a bridge.
Written, then ATTACKED by three lenses on the real code. What they measured
was worth it.
/etc/hosts was rewritten in TWO writes — "sed -i" then "printf >>" — while the
docstring promised the opposite. Sed refused and append succeeded: the
127.0.1.1 line survived FIRST and ours was added once per attempt; sed
succeeded and append refused: the host lost its own name entry, and every sudo
then waited on the resolver. It is now a complete file built in a temporary,
VERIFIED, then copied over — "cat >" not "mv", which would replace the inode
and lose mode and owner.
The final check relied on "getent hosts", which succeeds via mDNS even when
nothing was written — and accepted the fe80:: our own code rejects. It now
re-reads what was written.
awk replaces sed for filtering: "print" emits a newline, so an /etc/hosts with
no final one — cloud-init omits it — gets normalised. Without that our line
glued onto the previous one and the node's name pointed at another machine's
address.
Three more of the same kind. pmxcfs's dependents are restarted too: active
throughout the outage, they failed on ipcc_send_rec, and leaving them gave a
GUI in "communication failure" right after our ✓. A silent link is no longer
read as a missing mount. And the address is only blamed if pve-cluster
actually started.
The tests execute the commands instead of reading them, with stubs able to
FAIL: refused write, file with no final newline, tabs, a start that fails, a
mount that vanishes during reconfirmation. Proven by mutation — three
HOSTS-KO turned into HOSTS-OK make the test go red.
Assisted-by: Claude Opus 5
(cherry picked from commit d4f9358c6cb562029cc2ca9eb478d80c6a0a19a4)
2026-08-26 03:19:58 -04:00
|
|
|
|
def _pve_cluster_state(self, host):
|
|
|
|
|
|
"""L'état du cluster, LU UNE FOIS, plus ce qu'on peut en faire.
|
|
|
|
|
|
|
|
|
|
|
|
Rend (etat, quoi) où `quoi` vaut "" (rien à proposer), "hosts" (le
|
|
|
|
|
|
résolveur est en cause, une réécriture est justifiée) ou "unites" (le
|
|
|
|
|
|
nom résout déjà, seules les unités sont à terre).
|
|
|
|
|
|
|
|
|
|
|
|
Séparer les deux décisions, et non les fondre dans une garde unique :
|
|
|
|
|
|
interrompue après la réécriture de /etc/hosts, la réparation laissait
|
|
|
|
|
|
un hôte à un « systemctl start » de fonctionner — et la garde d'avant,
|
|
|
|
|
|
qui sortait dès que « routables » était non vide, refusait alors de le
|
|
|
|
|
|
finir. L'outil savait exactement quoi faire et s'y refusait
|
|
|
|
|
|
définitivement.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
_c, out = pve.run(host, pve.CLUSTER_CHECK_CMD, 40)
|
|
|
|
|
|
etat = pve.parse_cluster_check(out)
|
|
|
|
|
|
if not etat["lu"] or etat["monte"]:
|
|
|
|
|
|
return etat, ""
|
|
|
|
|
|
return etat, ("unites" if etat["routables"] else "hosts")
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_cluster_reason(self, host, etat=None, quoi=None):
|
[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
|
|
|
|
"""Pourquoi il n'y a AUCUN stockage. Liste vide si tout va bien.
|
|
|
|
|
|
|
|
|
|
|
|
« Il manque le stockage » est un symptôme, pas une cause : « pvesm »
|
|
|
|
|
|
ne parle qu'à travers /etc/pve, monté par pmxcfs. pmxcfs à terre, la
|
|
|
|
|
|
liste est vide et l'écran s'arrête sur le symptôme — le défaut est
|
|
|
|
|
|
trois étages plus bas, et il a fallu lire un journal pour le trouver.
|
|
|
|
|
|
|
[ADD] proxmox : l'écran remet pmxcfs debout lui-même
Le conseil « rejouer install_proxmox.sh sur l'hôte » ne pouvait PAS marcher :
la VM clone le dépôt distant, donc sa copie du script est celle du distant —
tant que le correctif n'y est pas, celle qui ne corrige rien. Trois hôtes de
suite sont tombés dessus, avec le même message inutile. L'écran répare donc :
gel de cloud-init, réécriture de /etc/hosts, relance des unités, constat du
montage — le pendant exact de l'offre de créer un pont.
Écrit, puis ATTAQUÉ par trois lentilles sur le code réel. Ce qu'elles ont
mesuré valait la peine.
/etc/hosts se réécrivait en DEUX écritures — « sed -i » puis « printf >> » —
alors que la docstring promettait l'inverse. Sed refusé et ajout réussi, la
ligne 127.0.1.1 survivait EN PREMIER et la nôtre s'ajoutait une fois par
tentative ; sed réussi et ajout refusé, l'hôte perdait l'entrée de son nom, et
chaque sudo y attendait ensuite le résolveur. C'est maintenant un fichier
complet bâti dans un temporaire, VÉRIFIÉ, puis recopié — « cat > » et non
« mv », qui remplacerait l'inode et perdrait mode et propriétaire.
Le contrôle final s'en remettait à « getent hosts », qui réussit via mDNS même
quand rien n'a été écrit — et acceptait les fe80:: que notre propre code
rejette. Il relit désormais ce qui a été écrit.
awk remplace sed pour filtrer : « print » émet un saut de ligne, donc un
/etc/hosts non terminé par un — cloud-init n'en met pas — est normalisé. Sans
ça notre ligne se collait à la précédente et le nom du nœud partait sur
l'adresse d'une autre machine.
Trois autres, du même acabit. Les dépendants de pmxcfs sont relancés eux
aussi : actifs pendant la panne, ils échouaient sur ipcc_send_rec, et les
laisser donnait une GUI en « communication failure » juste après notre ✓. Un
silence du lien n'est plus lu comme une absence de montage. Et l'adresse n'est
mise en cause que si pve-cluster a réellement démarré.
Les tests exécutent les commandes au lieu de les relire, bouchons capables
d'ÉCHOUER : écriture refusée, fichier sans saut de ligne final, tabulations,
start qui rate, montage qui disparaît pendant la reconfirmation. Prouvé par
mutation — trois HOSTS-KO changés en HOSTS-OK font rougir le test.
--- EN ---
The advice "replay install_proxmox.sh on the host" could NOT work: the VM
clones the remote, so its copy of the script is the remote's — while the fix
is not there, the one that fixes nothing. Three hosts in a row hit it with the
same useless message. So the screen repairs: freeze cloud-init, rewrite
/etc/hosts, restart the units, verify the mount — the exact counterpart of the
offer to create a bridge.
Written, then ATTACKED by three lenses on the real code. What they measured
was worth it.
/etc/hosts was rewritten in TWO writes — "sed -i" then "printf >>" — while the
docstring promised the opposite. Sed refused and append succeeded: the
127.0.1.1 line survived FIRST and ours was added once per attempt; sed
succeeded and append refused: the host lost its own name entry, and every sudo
then waited on the resolver. It is now a complete file built in a temporary,
VERIFIED, then copied over — "cat >" not "mv", which would replace the inode
and lose mode and owner.
The final check relied on "getent hosts", which succeeds via mDNS even when
nothing was written — and accepted the fe80:: our own code rejects. It now
re-reads what was written.
awk replaces sed for filtering: "print" emits a newline, so an /etc/hosts with
no final one — cloud-init omits it — gets normalised. Without that our line
glued onto the previous one and the node's name pointed at another machine's
address.
Three more of the same kind. pmxcfs's dependents are restarted too: active
throughout the outage, they failed on ipcc_send_rec, and leaving them gave a
GUI in "communication failure" right after our ✓. A silent link is no longer
read as a missing mount. And the address is only blamed if pve-cluster
actually started.
The tests execute the commands instead of reading them, with stubs able to
FAIL: refused write, file with no final newline, tabs, a start that fails, a
mount that vanishes during reconfirmation. Proven by mutation — three
HOSTS-KO turned into HOSTS-OK make the test go red.
Assisted-by: Claude Opus 5
(cherry picked from commit d4f9358c6cb562029cc2ca9eb478d80c6a0a19a4)
2026-08-26 03:19:58 -04:00
|
|
|
|
`etat`/`quoi` viennent de `_pve_cluster_state` quand l'appelant l'a
|
|
|
|
|
|
déjà interrogé : une seule sonde, et surtout un seul verdict. Sondé
|
|
|
|
|
|
deux fois, on annonçait une réparation que la seconde lecture
|
|
|
|
|
|
refusait ensuite d'offrir — une promesse suivie de rien.
|
|
|
|
|
|
"""
|
|
|
|
|
|
if etat is None:
|
|
|
|
|
|
etat, quoi = self._pve_cluster_state(host)
|
[FIX] proxmox : ne pas démarrer le pare-feu depuis l'extérieur
Une révision adversariale de la réparation à distance a rendu un constat que
ses TROIS lentilles — réseau, systemd, shell — ont trouvé indépendamment :
démarrer pve-firewall peut couper le ssh qui répare. Sa configuration vit dans
/var/lib/pve-cluster/config.db, donc elle est invisible tant que /etc/pve
n'est pas monté — c'est-à-dire exactement dans l'état qu'on répare. On
appliquerait des règles qu'on ne peut pas lire, sur la seule voie d'accès à la
machine.
Il n'est pas nécessaire au but : le stockage et le suivi demandent pve-cluster
et pvestatd, l'interface web pveproxy. Il repartira au prochain démarrage,
quand /etc/pve sera monté à temps. Le retirer de la liste coûte donc rien et
supprime le seul geste qui pouvait isoler un hôte.
Deux autres constats de la même révision, également réels.
Le gel de cloud-init gardait sur l'EXISTENCE du fichier. Or « printf … > » le
TRONQUE avant d'écrire : une coupure au mauvais moment laisse zéro octet, et
la garde annonce « déjà gelé » pour toujours. cloud-init continue de remettre
127.0.1.1 à chaque démarrage et le défaut redevient invisible — celui-là même
que ce code existe pour supprimer. La garde porte maintenant sur le CONTENU.
Et les adresses de lien-local passaient pour routables. Mesuré : « hostname
--ip-address » peut ne rendre QUE des fe80::, et une APIPA en 169.254 passait
le seul test « ne commence pas par 127. ». pmxcfs n'a alors rien
d'utilisable, mais le diagnostic concluait l'inverse et renvoyait vers
journalctl au lieu de /etc/hosts.
Enfin « la sonde n'a pas répondu » n'est plus lu comme « rien n'est monté » :
un dépassement de délai rend les mêmes vides, et on affirmait une cause qu'on
n'avait pas constatée.
--- EN ---
An adversarial review of the remote repair produced one finding all THREE of
its lenses — network, systemd, shell — reached independently: starting
pve-firewall can cut the ssh doing the repair. Its configuration lives in
/var/lib/pve-cluster/config.db, so it is invisible while /etc/pve is unmounted
— exactly the state being repaired. We would apply rules we cannot read, over
the machine's only way in.
It is not needed for the goal: storage and monitoring need pve-cluster and
pvestatd, the web interface pveproxy. It will come back at the next boot, when
/etc/pve mounts in time. Removing it from the list costs nothing and removes
the one gesture that could isolate a host.
Two more findings from the same review, equally real.
The cloud-init freeze guarded on the file's EXISTENCE. But "printf … >"
TRUNCATES before writing: an ill-timed cut leaves zero bytes, and the guard
then reports "already frozen" forever. cloud-init keeps putting 127.0.1.1 back
at every boot and the defect becomes invisible again — the very one this code
exists to remove. The guard now looks at the CONTENT.
And link-local addresses counted as routable. Measured: "hostname
--ip-address" can return ONLY fe80:: entries, and an APIPA 169.254 passed the
lone "does not start with 127." test. pmxcfs then has nothing usable, yet the
diagnosis concluded the opposite and pointed at journalctl instead of
/etc/hosts.
Finally "the probe did not answer" is no longer read as "nothing is mounted": a
timeout returns the same emptiness, and we were asserting a cause we had not
measured.
Assisted-by: Claude Opus 5
(cherry picked from commit fa9fb729d82e8d1a8e4fb549cc8061c7281b5dcb)
2026-08-25 06:31:30 -04:00
|
|
|
|
if not etat["lu"]:
|
|
|
|
|
|
return [
|
|
|
|
|
|
f"⚠ {t('The cluster probe did not answer: cause unknown.')}"
|
|
|
|
|
|
]
|
[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
|
|
|
|
if etat["monte"]:
|
|
|
|
|
|
return []
|
|
|
|
|
|
lignes = [
|
|
|
|
|
|
f"✗ {t('pve-cluster is down: /etc/pve is not mounted.')}",
|
|
|
|
|
|
f" {t('Without it pvesm answers nothing, hence no storage.')}",
|
|
|
|
|
|
]
|
[ADD] proxmox : l'écran remet pmxcfs debout lui-même
Le conseil « rejouer install_proxmox.sh sur l'hôte » ne pouvait PAS marcher :
la VM clone le dépôt distant, donc sa copie du script est celle du distant —
tant que le correctif n'y est pas, celle qui ne corrige rien. Trois hôtes de
suite sont tombés dessus, avec le même message inutile. L'écran répare donc :
gel de cloud-init, réécriture de /etc/hosts, relance des unités, constat du
montage — le pendant exact de l'offre de créer un pont.
Écrit, puis ATTAQUÉ par trois lentilles sur le code réel. Ce qu'elles ont
mesuré valait la peine.
/etc/hosts se réécrivait en DEUX écritures — « sed -i » puis « printf >> » —
alors que la docstring promettait l'inverse. Sed refusé et ajout réussi, la
ligne 127.0.1.1 survivait EN PREMIER et la nôtre s'ajoutait une fois par
tentative ; sed réussi et ajout refusé, l'hôte perdait l'entrée de son nom, et
chaque sudo y attendait ensuite le résolveur. C'est maintenant un fichier
complet bâti dans un temporaire, VÉRIFIÉ, puis recopié — « cat > » et non
« mv », qui remplacerait l'inode et perdrait mode et propriétaire.
Le contrôle final s'en remettait à « getent hosts », qui réussit via mDNS même
quand rien n'a été écrit — et acceptait les fe80:: que notre propre code
rejette. Il relit désormais ce qui a été écrit.
awk remplace sed pour filtrer : « print » émet un saut de ligne, donc un
/etc/hosts non terminé par un — cloud-init n'en met pas — est normalisé. Sans
ça notre ligne se collait à la précédente et le nom du nœud partait sur
l'adresse d'une autre machine.
Trois autres, du même acabit. Les dépendants de pmxcfs sont relancés eux
aussi : actifs pendant la panne, ils échouaient sur ipcc_send_rec, et les
laisser donnait une GUI en « communication failure » juste après notre ✓. Un
silence du lien n'est plus lu comme une absence de montage. Et l'adresse n'est
mise en cause que si pve-cluster a réellement démarré.
Les tests exécutent les commandes au lieu de les relire, bouchons capables
d'ÉCHOUER : écriture refusée, fichier sans saut de ligne final, tabulations,
start qui rate, montage qui disparaît pendant la reconfirmation. Prouvé par
mutation — trois HOSTS-KO changés en HOSTS-OK font rougir le test.
--- EN ---
The advice "replay install_proxmox.sh on the host" could NOT work: the VM
clones the remote, so its copy of the script is the remote's — while the fix
is not there, the one that fixes nothing. Three hosts in a row hit it with the
same useless message. So the screen repairs: freeze cloud-init, rewrite
/etc/hosts, restart the units, verify the mount — the exact counterpart of the
offer to create a bridge.
Written, then ATTACKED by three lenses on the real code. What they measured
was worth it.
/etc/hosts was rewritten in TWO writes — "sed -i" then "printf >>" — while the
docstring promised the opposite. Sed refused and append succeeded: the
127.0.1.1 line survived FIRST and ours was added once per attempt; sed
succeeded and append refused: the host lost its own name entry, and every sudo
then waited on the resolver. It is now a complete file built in a temporary,
VERIFIED, then copied over — "cat >" not "mv", which would replace the inode
and lose mode and owner.
The final check relied on "getent hosts", which succeeds via mDNS even when
nothing was written — and accepted the fe80:: our own code rejects. It now
re-reads what was written.
awk replaces sed for filtering: "print" emits a newline, so an /etc/hosts with
no final one — cloud-init omits it — gets normalised. Without that our line
glued onto the previous one and the node's name pointed at another machine's
address.
Three more of the same kind. pmxcfs's dependents are restarted too: active
throughout the outage, they failed on ipcc_send_rec, and leaving them gave a
GUI in "communication failure" right after our ✓. A silent link is no longer
read as a missing mount. And the address is only blamed if pve-cluster
actually started.
The tests execute the commands instead of reading them, with stubs able to
FAIL: refused write, file with no final newline, tabs, a start that fails, a
mount that vanishes during reconfirmation. Proven by mutation — three
HOSTS-KO turned into HOSTS-OK make the test go red.
Assisted-by: Claude Opus 5
(cherry picked from commit d4f9358c6cb562029cc2ca9eb478d80c6a0a19a4)
2026-08-26 03:19:58 -04:00
|
|
|
|
if quoi == "hosts":
|
[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
|
|
|
|
lignes += [
|
|
|
|
|
|
f" {t('The hostname only resolves to')}"
|
|
|
|
|
|
f" {' '.join(etat['adresses']) or '?'}"
|
|
|
|
|
|
f" — {t('pmxcfs needs a routable address.')}",
|
|
|
|
|
|
f" {t('cloud-init rewrites /etc/hosts at every boot.')}",
|
|
|
|
|
|
]
|
[ADD] proxmox : l'écran remet pmxcfs debout lui-même
Le conseil « rejouer install_proxmox.sh sur l'hôte » ne pouvait PAS marcher :
la VM clone le dépôt distant, donc sa copie du script est celle du distant —
tant que le correctif n'y est pas, celle qui ne corrige rien. Trois hôtes de
suite sont tombés dessus, avec le même message inutile. L'écran répare donc :
gel de cloud-init, réécriture de /etc/hosts, relance des unités, constat du
montage — le pendant exact de l'offre de créer un pont.
Écrit, puis ATTAQUÉ par trois lentilles sur le code réel. Ce qu'elles ont
mesuré valait la peine.
/etc/hosts se réécrivait en DEUX écritures — « sed -i » puis « printf >> » —
alors que la docstring promettait l'inverse. Sed refusé et ajout réussi, la
ligne 127.0.1.1 survivait EN PREMIER et la nôtre s'ajoutait une fois par
tentative ; sed réussi et ajout refusé, l'hôte perdait l'entrée de son nom, et
chaque sudo y attendait ensuite le résolveur. C'est maintenant un fichier
complet bâti dans un temporaire, VÉRIFIÉ, puis recopié — « cat > » et non
« mv », qui remplacerait l'inode et perdrait mode et propriétaire.
Le contrôle final s'en remettait à « getent hosts », qui réussit via mDNS même
quand rien n'a été écrit — et acceptait les fe80:: que notre propre code
rejette. Il relit désormais ce qui a été écrit.
awk remplace sed pour filtrer : « print » émet un saut de ligne, donc un
/etc/hosts non terminé par un — cloud-init n'en met pas — est normalisé. Sans
ça notre ligne se collait à la précédente et le nom du nœud partait sur
l'adresse d'une autre machine.
Trois autres, du même acabit. Les dépendants de pmxcfs sont relancés eux
aussi : actifs pendant la panne, ils échouaient sur ipcc_send_rec, et les
laisser donnait une GUI en « communication failure » juste après notre ✓. Un
silence du lien n'est plus lu comme une absence de montage. Et l'adresse n'est
mise en cause que si pve-cluster a réellement démarré.
Les tests exécutent les commandes au lieu de les relire, bouchons capables
d'ÉCHOUER : écriture refusée, fichier sans saut de ligne final, tabulations,
start qui rate, montage qui disparaît pendant la reconfirmation. Prouvé par
mutation — trois HOSTS-KO changés en HOSTS-OK font rougir le test.
--- EN ---
The advice "replay install_proxmox.sh on the host" could NOT work: the VM
clones the remote, so its copy of the script is the remote's — while the fix
is not there, the one that fixes nothing. Three hosts in a row hit it with the
same useless message. So the screen repairs: freeze cloud-init, rewrite
/etc/hosts, restart the units, verify the mount — the exact counterpart of the
offer to create a bridge.
Written, then ATTACKED by three lenses on the real code. What they measured
was worth it.
/etc/hosts was rewritten in TWO writes — "sed -i" then "printf >>" — while the
docstring promised the opposite. Sed refused and append succeeded: the
127.0.1.1 line survived FIRST and ours was added once per attempt; sed
succeeded and append refused: the host lost its own name entry, and every sudo
then waited on the resolver. It is now a complete file built in a temporary,
VERIFIED, then copied over — "cat >" not "mv", which would replace the inode
and lose mode and owner.
The final check relied on "getent hosts", which succeeds via mDNS even when
nothing was written — and accepted the fe80:: our own code rejects. It now
re-reads what was written.
awk replaces sed for filtering: "print" emits a newline, so an /etc/hosts with
no final one — cloud-init omits it — gets normalised. Without that our line
glued onto the previous one and the node's name pointed at another machine's
address.
Three more of the same kind. pmxcfs's dependents are restarted too: active
throughout the outage, they failed on ipcc_send_rec, and leaving them gave a
GUI in "communication failure" right after our ✓. A silent link is no longer
read as a missing mount. And the address is only blamed if pve-cluster
actually started.
The tests execute the commands instead of reading them, with stubs able to
FAIL: refused write, file with no final newline, tabs, a start that fails, a
mount that vanishes during reconfirmation. Proven by mutation — three
HOSTS-KO turned into HOSTS-OK make the test go red.
Assisted-by: Claude Opus 5
(cherry picked from commit d4f9358c6cb562029cc2ca9eb478d80c6a0a19a4)
2026-08-26 03:19:58 -04:00
|
|
|
|
else:
|
|
|
|
|
|
# Le nom résout déjà : le résolveur n'est PAS en cause, et le dire
|
|
|
|
|
|
# évite d'envoyer réécrire un fichier système pour rien.
|
|
|
|
|
|
lignes.append(
|
|
|
|
|
|
f" {t('The hostname resolves fine: only the units are down.')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
# La promesse UNIQUEMENT si l'offre suivra. Sinon on disait « cet écran
|
|
|
|
|
|
# peut le réparer » puis plus rien du tout.
|
|
|
|
|
|
lignes.append(f"→ {t('This screen can repair it (see below).')}")
|
[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
|
|
|
|
return lignes
|
|
|
|
|
|
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
|
def _pve_depth(self, host):
|
|
|
|
|
|
"""À quel étage d'imbrication se trouve cet hôte. 1 = machine réelle.
|
|
|
|
|
|
|
|
|
|
|
|
Comptée sur la chaîne de ProxyJump : un rebond par étage. C'est nous
|
|
|
|
|
|
qui écrivons ces entrées, donc la mesure est exacte pour notre parc.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import nesting
|
|
|
|
|
|
|
|
|
|
|
|
return nesting.depth_from_jumps(
|
|
|
|
|
|
self._ssh_jump_depth(host.get("target") or "")
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_depth_note(self, host, cpu):
|
|
|
|
|
|
"""(cpu borné, lignes à dire). Ce que la profondeur impose.
|
|
|
|
|
|
|
|
|
|
|
|
L'écran lisait la capacité de l'HÔTE et l'offrait en entier. Sur un
|
|
|
|
|
|
troisième étage à 14 cœurs, il a proposé 12 vCPU — et la VM n'a jamais
|
|
|
|
|
|
démarré : même RIP à trois relevés deux minutes d'écart, pas un octet
|
|
|
|
|
|
lu de plus. Le nombre n'était pas absurde pour la machine ; il l'était
|
|
|
|
|
|
pour sa profondeur.
|
|
|
|
|
|
|
|
|
|
|
|
Un seul levier, le vCPU : la même VM gelait au MÊME octet avec 9 Go et
|
|
|
|
|
|
avec 2 Go, donc rogner la mémoire ne gagnerait rien et priverait
|
|
|
|
|
|
l'étage suivant.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import nesting
|
|
|
|
|
|
|
|
|
|
|
|
profondeur = self._pve_depth(host)
|
|
|
|
|
|
borne, _ram, raison = nesting.capped_for_depth(profondeur, cpu, 0)
|
|
|
|
|
|
if profondeur <= nesting.PROFONDEUR_SURE:
|
|
|
|
|
|
return cpu, []
|
|
|
|
|
|
lignes = [
|
|
|
|
|
|
f"⚠ {t('Nesting level')} {profondeur} —"
|
|
|
|
|
|
f" {t('vendors document two, not more.')}",
|
|
|
|
|
|
f" {t('Measured at level 4: 36x slower, then a frozen kernel.')}",
|
|
|
|
|
|
]
|
|
|
|
|
|
if raison:
|
|
|
|
|
|
lignes.append(f" {t('vCPU capped to')} {borne}")
|
|
|
|
|
|
return borne, lignes
|
|
|
|
|
|
|
[ADD] proxmox : l'écran remet pmxcfs debout lui-même
Le conseil « rejouer install_proxmox.sh sur l'hôte » ne pouvait PAS marcher :
la VM clone le dépôt distant, donc sa copie du script est celle du distant —
tant que le correctif n'y est pas, celle qui ne corrige rien. Trois hôtes de
suite sont tombés dessus, avec le même message inutile. L'écran répare donc :
gel de cloud-init, réécriture de /etc/hosts, relance des unités, constat du
montage — le pendant exact de l'offre de créer un pont.
Écrit, puis ATTAQUÉ par trois lentilles sur le code réel. Ce qu'elles ont
mesuré valait la peine.
/etc/hosts se réécrivait en DEUX écritures — « sed -i » puis « printf >> » —
alors que la docstring promettait l'inverse. Sed refusé et ajout réussi, la
ligne 127.0.1.1 survivait EN PREMIER et la nôtre s'ajoutait une fois par
tentative ; sed réussi et ajout refusé, l'hôte perdait l'entrée de son nom, et
chaque sudo y attendait ensuite le résolveur. C'est maintenant un fichier
complet bâti dans un temporaire, VÉRIFIÉ, puis recopié — « cat > » et non
« mv », qui remplacerait l'inode et perdrait mode et propriétaire.
Le contrôle final s'en remettait à « getent hosts », qui réussit via mDNS même
quand rien n'a été écrit — et acceptait les fe80:: que notre propre code
rejette. Il relit désormais ce qui a été écrit.
awk remplace sed pour filtrer : « print » émet un saut de ligne, donc un
/etc/hosts non terminé par un — cloud-init n'en met pas — est normalisé. Sans
ça notre ligne se collait à la précédente et le nom du nœud partait sur
l'adresse d'une autre machine.
Trois autres, du même acabit. Les dépendants de pmxcfs sont relancés eux
aussi : actifs pendant la panne, ils échouaient sur ipcc_send_rec, et les
laisser donnait une GUI en « communication failure » juste après notre ✓. Un
silence du lien n'est plus lu comme une absence de montage. Et l'adresse n'est
mise en cause que si pve-cluster a réellement démarré.
Les tests exécutent les commandes au lieu de les relire, bouchons capables
d'ÉCHOUER : écriture refusée, fichier sans saut de ligne final, tabulations,
start qui rate, montage qui disparaît pendant la reconfirmation. Prouvé par
mutation — trois HOSTS-KO changés en HOSTS-OK font rougir le test.
--- EN ---
The advice "replay install_proxmox.sh on the host" could NOT work: the VM
clones the remote, so its copy of the script is the remote's — while the fix
is not there, the one that fixes nothing. Three hosts in a row hit it with the
same useless message. So the screen repairs: freeze cloud-init, rewrite
/etc/hosts, restart the units, verify the mount — the exact counterpart of the
offer to create a bridge.
Written, then ATTACKED by three lenses on the real code. What they measured
was worth it.
/etc/hosts was rewritten in TWO writes — "sed -i" then "printf >>" — while the
docstring promised the opposite. Sed refused and append succeeded: the
127.0.1.1 line survived FIRST and ours was added once per attempt; sed
succeeded and append refused: the host lost its own name entry, and every sudo
then waited on the resolver. It is now a complete file built in a temporary,
VERIFIED, then copied over — "cat >" not "mv", which would replace the inode
and lose mode and owner.
The final check relied on "getent hosts", which succeeds via mDNS even when
nothing was written — and accepted the fe80:: our own code rejects. It now
re-reads what was written.
awk replaces sed for filtering: "print" emits a newline, so an /etc/hosts with
no final one — cloud-init omits it — gets normalised. Without that our line
glued onto the previous one and the node's name pointed at another machine's
address.
Three more of the same kind. pmxcfs's dependents are restarted too: active
throughout the outage, they failed on ipcc_send_rec, and leaving them gave a
GUI in "communication failure" right after our ✓. A silent link is no longer
read as a missing mount. And the address is only blamed if pve-cluster
actually started.
The tests execute the commands instead of reading them, with stubs able to
FAIL: refused write, file with no final newline, tabs, a start that fails, a
mount that vanishes during reconfirmation. Proven by mutation — three
HOSTS-KO turned into HOSTS-OK make the test go red.
Assisted-by: Claude Opus 5
(cherry picked from commit d4f9358c6cb562029cc2ca9eb478d80c6a0a19a4)
2026-08-26 03:19:58 -04:00
|
|
|
|
def _pve_ssh_ip(self, host):
|
|
|
|
|
|
"""Adresse par laquelle NOTRE ssh atteint l'hôte, ou "".
|
|
|
|
|
|
|
|
|
|
|
|
Lue SANS privilège, exprès : « sudo » remet l'environnement à zéro et
|
|
|
|
|
|
efface $SSH_CONNECTION. Cette lecture n'a besoin d'aucun droit.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
_c, out = pve.run(
|
|
|
|
|
|
dict(host, sudo=""), 'printf %s "$SSH_CONNECTION"', 20
|
|
|
|
|
|
)
|
|
|
|
|
|
return pve.ssh_server_ip(out)
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_restart_units(self, remonte):
|
|
|
|
|
|
"""Relance les unités et rend (pivot_ok, lignes_de_cause).
|
|
|
|
|
|
|
|
|
|
|
|
`remonte` : /etc/pve était absent, donc les dépendants qui SEMBLENT
|
|
|
|
|
|
actifs parlaient à un pmxcfs mort. Leur état actif ne prouve rien sur
|
|
|
|
|
|
leur lien à pmxcfs — le même raisonnement qui impose un « restart » à
|
|
|
|
|
|
pve-cluster vaut pour eux, et sans cela la GUI répondait
|
|
|
|
|
|
« communication failure » après un ✓.
|
|
|
|
|
|
|
|
|
|
|
|
La sortie de chaque unité est LUE. pve-cluster est le pivot : les
|
|
|
|
|
|
trois suivantes le requièrent, donc s'il échoue, poursuivre ne produit
|
|
|
|
|
|
que soixante lignes de journal après la vraie cause.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
for unite in pve.PVE_UNITS:
|
|
|
|
|
|
_c, out = self._pve_show(
|
|
|
|
|
|
pve.pve_unit_cmd(unite, remonte=remonte), timeout=200
|
|
|
|
|
|
)
|
|
|
|
|
|
texte = pve.strip_ssh_noise(out)
|
|
|
|
|
|
if f"KO {unite}" in texte or f"SKIP {unite}" in texte:
|
|
|
|
|
|
if unite == "pve-cluster":
|
|
|
|
|
|
lignes = [
|
|
|
|
|
|
ligne
|
|
|
|
|
|
for ligne in texte.strip().splitlines()
|
|
|
|
|
|
if ligne.strip()
|
|
|
|
|
|
]
|
|
|
|
|
|
return False, lignes[-8:]
|
|
|
|
|
|
return True, []
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_offer_cluster_fix(self, host, etat=None, quoi=None):
|
|
|
|
|
|
"""Propose de remettre pmxcfs debout, et le fait. Rend True si /etc/pve
|
|
|
|
|
|
est monté à la sortie.
|
|
|
|
|
|
|
|
|
|
|
|
Le pendant de `_pve_offer_bridge`, et pour la même raison : le terminal
|
|
|
|
|
|
est encore à nous, donc c'est ICI qu'on peut poser la question et
|
|
|
|
|
|
montrer ce qu'on exécute.
|
|
|
|
|
|
|
|
|
|
|
|
Pourquoi le faire au lieu de conseiller : le conseil était « rejouer
|
|
|
|
|
|
install_proxmox.sh sur l'hôte », et il ne pouvait PAS marcher. La VM
|
|
|
|
|
|
clone le dépôt distant, donc sa copie du script est celle du distant —
|
|
|
|
|
|
c'est-à-dire, tant que le correctif n'est pas poussé, celle qui ne
|
|
|
|
|
|
corrige rien. Trois hôtes de suite sont tombés dessus.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
if etat is None:
|
|
|
|
|
|
etat, quoi = self._pve_cluster_state(host)
|
|
|
|
|
|
if not quoi:
|
|
|
|
|
|
return bool(etat["monte"])
|
|
|
|
|
|
print(f"\n {t('Repair it from here?')}")
|
|
|
|
|
|
if quoi == "hosts":
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" [1] {t('freeze cloud-init, fix /etc/hosts, restart pmxcfs')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f" [1] {t('restart pmxcfs only (the address is fine)')}")
|
|
|
|
|
|
print(f" [0] {t('leave it alone')}")
|
|
|
|
|
|
if input(t("Choice: ")).strip() != "1":
|
|
|
|
|
|
return False
|
|
|
|
|
|
if quoi == "hosts":
|
|
|
|
|
|
ip = self._pve_ssh_ip(host)
|
|
|
|
|
|
if not ip:
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" ✗ {t('Cannot tell which address reaches this host.')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
return False
|
|
|
|
|
|
print(f" {t('address the node will answer for')} : {ip}")
|
|
|
|
|
|
# Le gel d'abord : sans lui la correction ne survit pas au
|
|
|
|
|
|
# prochain démarrage, et on aurait réparé pour une seule session.
|
|
|
|
|
|
for cmd in (
|
|
|
|
|
|
pve.cloud_hosts_freeze_cmd(),
|
|
|
|
|
|
pve.hosts_repair_cmd(ip),
|
|
|
|
|
|
):
|
|
|
|
|
|
code, sortie = self._pve_show(cmd, timeout=60)
|
|
|
|
|
|
if code or "-KO" in pve.strip_ssh_noise(sortie):
|
|
|
|
|
|
print(f" ✗ {t('Step failed, stopping here.')}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
pivot, cause = self._pve_restart_units(remonte=True)
|
|
|
|
|
|
if not pivot:
|
|
|
|
|
|
print(f" ✗ pve-cluster {t('would not start:')}")
|
|
|
|
|
|
for ligne in cause:
|
|
|
|
|
|
print(f" {ligne}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
_c, out = self._pve_show(pve.mount_wait_cmd(), timeout=120)
|
|
|
|
|
|
vu = pve.parse_mount_wait(out)
|
|
|
|
|
|
if vu["verdict"] == "MONTE":
|
|
|
|
|
|
print(f" ✓ /etc/pve {t('mounted')}")
|
|
|
|
|
|
return True
|
|
|
|
|
|
if vu["verdict"] == "INCONNU":
|
|
|
|
|
|
# Un silence du lien n'est PAS une absence de montage : conclure
|
|
|
|
|
|
# l'inverse envoie chercher dans journalctl une panne qui n'existe
|
|
|
|
|
|
# pas.
|
|
|
|
|
|
print(f" ⚠ {t('Cannot tell whether it mounted (link lost).')}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
if vu["verdict"] == "BATTEMENT":
|
|
|
|
|
|
# Monté puis reperdu : le dire, parce qu'un ✓ suivi d'un « pvesm ne
|
|
|
|
|
|
# répond plus » dix secondes après est le pire des deux.
|
|
|
|
|
|
print(f" ⚠ /etc/pve {t('mounted then lost again')}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f" ✗ /etc/pve {t('still absent')}")
|
|
|
|
|
|
# L'adresse n'est mise en cause que quand pve-cluster a DÉMARRÉ et que
|
|
|
|
|
|
# le montage manque quand même : c'est le seul cas où le résolveur
|
|
|
|
|
|
# peut l'expliquer.
|
|
|
|
|
|
if quoi == "hosts":
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" {t('The address written may not be the one pmxcfs needs.')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
[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
|
|
|
|
def _pve_internal_cidr(self, host):
|
|
|
|
|
|
"""Réseau du futur pont interne, CHOISI d'après l'hôte.
|
|
|
|
|
|
|
|
|
|
|
|
Pas une constante : un Proxmox dans un Proxmox hérite du réseau
|
2026-09-16 00:42:31 -04:00
|
|
|
|
interne de son parent, et l'adresse de INTERNAL_CIDR y est celle
|
|
|
|
|
|
de sa propre PASSERELLE. La poser sur son pont rend tout le /24 local, la
|
[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
|
|
|
|
passerelle devient injoignable, et la machine s'isole au milieu de la
|
|
|
|
|
|
commande qui la configure. Vécu : « ifup » n'a jamais rendu la main et
|
|
|
|
|
|
la VM ne répondait plus, ni en ssh ni en ping."""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
_c, out = pve.run(host, pve.USED_NETS_CMD, 40)
|
|
|
|
|
|
return pve.pick_internal_cidr(out)
|
|
|
|
|
|
|
[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
|
|
|
|
def _pve_nat_ready(self, host):
|
|
|
|
|
|
"""(prêt ?, lignes à dire). La table NAT existe-t-elle sur cet hôte ?
|
|
|
|
|
|
|
|
|
|
|
|
Posée ICI, au moment d'écrire un pont NAT, et non à la connexion : le
|
|
|
|
|
|
noyau est vérifié quand on confirme l'hôte, mais 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 doit être là où la conséquence
|
|
|
|
|
|
tombe.
|
|
|
|
|
|
|
|
|
|
|
|
Sans cette question, ifupdown2 rendait six lignes d'iptables et « code
|
|
|
|
|
|
de retour 1 », après avoir déjà écrit la strophe dans
|
|
|
|
|
|
/etc/network/interfaces. Rien dans ce bruit ne dit qu'il faut
|
|
|
|
|
|
redémarrer.
|
|
|
|
|
|
|
|
|
|
|
|
Le cas n'a rien d'exotique : notre propre install_proxmox.sh pose le
|
|
|
|
|
|
noyau Proxmox sans redémarrer — lancé par ssh, un reboot couperait la
|
|
|
|
|
|
session. Une Proxmox imbriquée fraîchement installée est donc TOUJOURS
|
|
|
|
|
|
dans cet état, sur le noyau cloud de Debian, qui est dépouillé de tout
|
|
|
|
|
|
netfilter."""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
_c, out = pve.run(host, pve.NAT_CHECK_CMD, 40)
|
|
|
|
|
|
etat = pve.parse_nat_check(out)
|
|
|
|
|
|
if etat["nat"]:
|
|
|
|
|
|
return True, []
|
|
|
|
|
|
lignes = [
|
|
|
|
|
|
f"✗ {t('No NAT table on this host: the bridge would lead nowhere.')}",
|
|
|
|
|
|
f" {t('Running kernel:')} {etat['kernel'] or '?'}",
|
|
|
|
|
|
]
|
|
|
|
|
|
if etat["pve_kernel"]:
|
|
|
|
|
|
lignes += [
|
|
|
|
|
|
f" {t('The distribution kernel carries no netfilter module.')}",
|
|
|
|
|
|
f" {t('Proxmox kernel installed:')} {etat['pve_kernel']}"
|
|
|
|
|
|
f" — {t('taken at next boot')}",
|
|
|
|
|
|
f"→ ssh {host.get('target', '')} sudo reboot,"
|
|
|
|
|
|
f" {t('then come back here.')}",
|
|
|
|
|
|
]
|
|
|
|
|
|
else:
|
|
|
|
|
|
lignes.append(
|
|
|
|
|
|
f" {t('No Proxmox kernel installed: finish the install first.')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
return False, lignes
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_nat_reason(self, host):
|
|
|
|
|
|
"""La même chose en UNE ligne, pour l'écran Textual."""
|
|
|
|
|
|
ok, lignes = self._pve_nat_ready(host)
|
|
|
|
|
|
if ok:
|
|
|
|
|
|
return ""
|
|
|
|
|
|
return " ".join(ligne.strip("✗→ ") for ligne in lignes[:2])
|
|
|
|
|
|
|
[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
|
|
|
|
def _pve_make_internal_bridge(self):
|
|
|
|
|
|
"""Crée le pont INTERNE et le rend, ou ('', raison). SANS rien demander.
|
|
|
|
|
|
|
|
|
|
|
|
Appelable depuis l'écran de déploiement, où Textual tient le terminal :
|
|
|
|
|
|
aucune invite, aucune sortie imprimée, tout est capturé. C'est possible
|
|
|
|
|
|
parce que ce pont ne touche AUCUNE interface physique — il n'y a donc
|
|
|
|
|
|
rien à faire arbitrer. Un pont sur le LAN, lui, déplace l'adresse de
|
|
|
|
|
|
l'hôte et coupe la session : il reste manuel, et l'écran le dit.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
host = self._pve_host(ask=False)
|
|
|
|
|
|
if not host:
|
|
|
|
|
|
return "", t("No Proxmox host.")
|
[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
|
|
|
|
# AVANT d'écrire quoi que ce soit : une strophe posée puis un
|
|
|
|
|
|
# « ifup » qui échoue laisse le fichier modifié et le pont absent.
|
|
|
|
|
|
raison = self._pve_nat_reason(host)
|
|
|
|
|
|
if raison:
|
|
|
|
|
|
return "", raison
|
[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
|
|
|
|
cidr = self._pve_internal_cidr(host)
|
|
|
|
|
|
if not cidr:
|
|
|
|
|
|
return "", t("No free subnet left for an internal bridge.")
|
[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
|
|
|
|
uplink = self._pve_uplink()
|
[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
|
|
|
|
for cmd in pve.bridge_setup_cmds(cidr=cidr, uplink=uplink):
|
[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
|
|
|
|
code, sortie = pve.run(host, cmd, 180)
|
|
|
|
|
|
if code:
|
|
|
|
|
|
lignes = pve.strip_ssh_noise(sortie).strip().splitlines()
|
|
|
|
|
|
return "", (lignes[-1] if lignes else t("Step failed"))
|
|
|
|
|
|
_c, out = pve.run(host, "ip -o link show type bridge", 30)
|
|
|
|
|
|
if pve.INTERNAL_BRIDGE not in pve.parse_bridges(out):
|
|
|
|
|
|
return "", t("The bridge did not come up.")
|
|
|
|
|
|
return pve.INTERNAL_BRIDGE, ""
|
|
|
|
|
|
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
def _pve_offer_bridge(self):
|
|
|
|
|
|
"""Aucun pont sur l'hôte : en proposer un, sans risquer l'accès.
|
|
|
|
|
|
|
|
|
|
|
|
Une Proxmox installée SUR Debian n'a pas de vmbr0 — l'ISO en crée un,
|
|
|
|
|
|
pas la procédure sur Debian. Or « qm create » exige un pont.
|
|
|
|
|
|
|
|
|
|
|
|
On ne propose donc PAS d'ajouter l'interface physique au pont : cela
|
|
|
|
|
|
déplace l'adresse de la machine et coupe la session SSH en cours, sans
|
|
|
|
|
|
retour possible à distance. Un pont INTERNE, lui, ne touche à rien —
|
|
|
|
|
|
les VM s'y parlent, et le masquerading leur donne l'extérieur.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
[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
|
|
|
|
host = self._pve_host(ask=False)
|
2026-09-16 00:42:31 -04:00
|
|
|
|
# Le réseau est LU sur l'hôte avant d'être proposé : annoncer le
|
|
|
|
|
|
# réseau par défaut pour en poser un autre serait mentir sur
|
|
|
|
|
|
# l'écran même où l'on demande l'accord.
|
[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
|
|
|
|
cidr = self._pve_internal_cidr(host) if host else pve.INTERNAL_CIDR
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
print(f"\n ⚠ {t('No network bridge on this host.')}")
|
|
|
|
|
|
print(f" {t('qm create needs one. Two ways:')}")
|
[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
|
|
|
|
if not cidr:
|
|
|
|
|
|
print(f" ✗ {t('No free subnet left for an internal bridge.')}")
|
|
|
|
|
|
print(f" {t('do it myself (bridge-ports <nic>, needs console)')}")
|
|
|
|
|
|
return ""
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
print(
|
|
|
|
|
|
f" [1] {t('create an internal')} {pve.INTERNAL_BRIDGE}"
|
[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
|
|
|
|
f" ({cidr}) + NAT — {t('touches no physical NIC')}"
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
)
|
|
|
|
|
|
print(f" [2] {t('do it myself (bridge-ports <nic>, needs console)')}")
|
|
|
|
|
|
if input(t("Choice: ")).strip() != "1":
|
|
|
|
|
|
print(f"\n {t('To bridge the LAN, on the host:')}")
|
|
|
|
|
|
print(" auto vmbr0")
|
|
|
|
|
|
print(" iface vmbr0 inet static")
|
|
|
|
|
|
print(" address <ip-de-l-hôte>/24")
|
|
|
|
|
|
print(" gateway <passerelle>")
|
|
|
|
|
|
print(" bridge-ports <interface>")
|
[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
|
|
|
|
print(
|
|
|
|
|
|
f" ⚠ {t('This moves the host address: do it from a console.')}"
|
|
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
return ""
|
[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
|
|
|
|
ok, lignes = self._pve_nat_ready(host) if host else (True, [])
|
|
|
|
|
|
if not ok:
|
|
|
|
|
|
print()
|
|
|
|
|
|
for ligne in lignes:
|
|
|
|
|
|
print(f" {ligne}")
|
|
|
|
|
|
return ""
|
[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
|
|
|
|
uplink = self._pve_uplink()
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
print(f" {t('uplink for NAT')} : {uplink or t('none')}")
|
[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
|
|
|
|
for cmd in pve.bridge_setup_cmds(cidr=cidr, uplink=uplink):
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
code, _o = self._pve_show(cmd, timeout=120)
|
|
|
|
|
|
if code:
|
|
|
|
|
|
print(f" ✗ {t('Step failed, stopping here.')}")
|
|
|
|
|
|
return ""
|
|
|
|
|
|
_c, out = self._pve_show("ip -o link show type bridge", quiet=True)
|
|
|
|
|
|
ponts = pve.parse_bridges(out)
|
|
|
|
|
|
if pve.INTERNAL_BRIDGE not in ponts:
|
|
|
|
|
|
print(f" ✗ {t('The bridge did not come up.')}")
|
|
|
|
|
|
return ""
|
|
|
|
|
|
print(f" ✓ {pve.INTERNAL_BRIDGE}")
|
|
|
|
|
|
return pve.INTERNAL_BRIDGE
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_deploy(self, dry_run=False):
|
|
|
|
|
|
"""Déploie une ou plusieurs VM SUR l'hôte Proxmox choisi.
|
|
|
|
|
|
|
|
|
|
|
|
L'écran d'abord, les invites en repli : c'est le même choix qu'en
|
|
|
|
|
|
QEMU/KVM, et pour la même raison — un plan de plusieurs machines se
|
|
|
|
|
|
vérifie d'un coup d'œil, pas en relisant vingt réponses déjà données.
|
|
|
|
|
|
Sans terminal graphique (ou sur refus), on retombe sur les questions.
|
|
|
|
|
|
"""
|
|
|
|
|
|
host = self._pve_host()
|
|
|
|
|
|
if not host:
|
|
|
|
|
|
return
|
|
|
|
|
|
mod = self._qemu_import_module()
|
|
|
|
|
|
try:
|
|
|
|
|
|
from script.todo.proxmox_deploy_form import run_proxmox_form
|
|
|
|
|
|
|
|
|
|
|
|
ctx = self._pve_form_context(mod, host)
|
|
|
|
|
|
spec = run_proxmox_form(ctx)
|
|
|
|
|
|
except ImportError as exc:
|
|
|
|
|
|
print(f" ⚠ {t('TUI unavailable')} : {exc}")
|
|
|
|
|
|
spec = {}
|
|
|
|
|
|
if spec is None:
|
|
|
|
|
|
print(t("Cancelled."))
|
|
|
|
|
|
return
|
|
|
|
|
|
if spec:
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
return self._pve_run_spec(host, spec, mod, dry_run)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
return self._pve_deploy_prompts(dry_run)
|
|
|
|
|
|
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
def _pve_run_spec(self, host, spec, mod, dry_run=False):
|
|
|
|
|
|
"""Enveloppe le déploiement de la coupure d'amont qu'il demande.
|
|
|
|
|
|
|
|
|
|
|
|
La coupure est la MÊME qu'en QEMU/KVM, et elle est posée ICI, pas sur
|
|
|
|
|
|
l'hôte distant : un hôte Proxmox qui porte l'autorité du cache est
|
|
|
|
|
|
une VM de ce pont, et ses invités sortent derrière son adresse. Les
|
|
|
|
|
|
règles qui coupent ce pont les couvrent donc tous.
|
|
|
|
|
|
|
|
|
|
|
|
Le formulaire n'offre la case que dans ce cas ; une spec qui la porte
|
|
|
|
|
|
quand même — invites textuelles, spec écrite à la main — coupe ce
|
|
|
|
|
|
pont-ci, ce qui reste vrai pour les VM qui le traversent.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.todo.qemu_deploy import _SansInternetImpossible
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
with self._qemu_sans_internet(bool(spec.get("offline"))) as coupee:
|
|
|
|
|
|
return self._pve_deploy_spec(
|
|
|
|
|
|
host, spec, mod, dry_run, coupee=coupee
|
|
|
|
|
|
)
|
|
|
|
|
|
except _SansInternetImpossible:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
def _pve_form_context(self, mod, host):
|
|
|
|
|
|
"""Tout ce que l'écran doit savoir, LU AVANT de l'ouvrir.
|
|
|
|
|
|
|
|
|
|
|
|
Chaque lecture passe par ssh, et certaines par sudo : une invite de
|
|
|
|
|
|
mot de passe pendant que Textual affiche casserait l'écran. On paie
|
|
|
|
|
|
donc tout ici, une fois, terminal encore à nous.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
print(f"\n{t('Loading (host, storage, bridges, VMs)...')}")
|
|
|
|
|
|
native = self._native_arch()
|
|
|
|
|
|
arches = ["amd64", "arm64", "s390x"]
|
|
|
|
|
|
if native not in arches:
|
|
|
|
|
|
arches.insert(0, native)
|
|
|
|
|
|
catalog = {}
|
|
|
|
|
|
for a in arches:
|
|
|
|
|
|
distros = list(mod.DISTROS)
|
|
|
|
|
|
allowed = self._qemu_arch_distros(a)
|
|
|
|
|
|
if allowed is not None:
|
|
|
|
|
|
distros = [d for d in distros if d in allowed]
|
|
|
|
|
|
entries = self._qemu_catalog_entries(mod, distros, a)
|
|
|
|
|
|
for e in entries:
|
|
|
|
|
|
e["name"] = self._qemu_infra_name(
|
|
|
|
|
|
e["distro"], e["version"], e["arch"]
|
|
|
|
|
|
)
|
|
|
|
|
|
catalog[a] = entries
|
|
|
|
|
|
|
|
|
|
|
|
vms = self._pve_vms()
|
|
|
|
|
|
_c, out = self._pve_show("pvesm status --content images", quiet=True)
|
|
|
|
|
|
stockages = pve.parse_storages(out)
|
[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
|
|
|
|
if not stockages:
|
|
|
|
|
|
# AVANT d'ouvrir l'écran : une fois Textual à l'affiche, ces
|
[ADD] proxmox : l'écran remet pmxcfs debout lui-même
Le conseil « rejouer install_proxmox.sh sur l'hôte » ne pouvait PAS marcher :
la VM clone le dépôt distant, donc sa copie du script est celle du distant —
tant que le correctif n'y est pas, celle qui ne corrige rien. Trois hôtes de
suite sont tombés dessus, avec le même message inutile. L'écran répare donc :
gel de cloud-init, réécriture de /etc/hosts, relance des unités, constat du
montage — le pendant exact de l'offre de créer un pont.
Écrit, puis ATTAQUÉ par trois lentilles sur le code réel. Ce qu'elles ont
mesuré valait la peine.
/etc/hosts se réécrivait en DEUX écritures — « sed -i » puis « printf >> » —
alors que la docstring promettait l'inverse. Sed refusé et ajout réussi, la
ligne 127.0.1.1 survivait EN PREMIER et la nôtre s'ajoutait une fois par
tentative ; sed réussi et ajout refusé, l'hôte perdait l'entrée de son nom, et
chaque sudo y attendait ensuite le résolveur. C'est maintenant un fichier
complet bâti dans un temporaire, VÉRIFIÉ, puis recopié — « cat > » et non
« mv », qui remplacerait l'inode et perdrait mode et propriétaire.
Le contrôle final s'en remettait à « getent hosts », qui réussit via mDNS même
quand rien n'a été écrit — et acceptait les fe80:: que notre propre code
rejette. Il relit désormais ce qui a été écrit.
awk remplace sed pour filtrer : « print » émet un saut de ligne, donc un
/etc/hosts non terminé par un — cloud-init n'en met pas — est normalisé. Sans
ça notre ligne se collait à la précédente et le nom du nœud partait sur
l'adresse d'une autre machine.
Trois autres, du même acabit. Les dépendants de pmxcfs sont relancés eux
aussi : actifs pendant la panne, ils échouaient sur ipcc_send_rec, et les
laisser donnait une GUI en « communication failure » juste après notre ✓. Un
silence du lien n'est plus lu comme une absence de montage. Et l'adresse n'est
mise en cause que si pve-cluster a réellement démarré.
Les tests exécutent les commandes au lieu de les relire, bouchons capables
d'ÉCHOUER : écriture refusée, fichier sans saut de ligne final, tabulations,
start qui rate, montage qui disparaît pendant la reconfirmation. Prouvé par
mutation — trois HOSTS-KO changés en HOSTS-OK font rougir le test.
--- EN ---
The advice "replay install_proxmox.sh on the host" could NOT work: the VM
clones the remote, so its copy of the script is the remote's — while the fix
is not there, the one that fixes nothing. Three hosts in a row hit it with the
same useless message. So the screen repairs: freeze cloud-init, rewrite
/etc/hosts, restart the units, verify the mount — the exact counterpart of the
offer to create a bridge.
Written, then ATTACKED by three lenses on the real code. What they measured
was worth it.
/etc/hosts was rewritten in TWO writes — "sed -i" then "printf >>" — while the
docstring promised the opposite. Sed refused and append succeeded: the
127.0.1.1 line survived FIRST and ours was added once per attempt; sed
succeeded and append refused: the host lost its own name entry, and every sudo
then waited on the resolver. It is now a complete file built in a temporary,
VERIFIED, then copied over — "cat >" not "mv", which would replace the inode
and lose mode and owner.
The final check relied on "getent hosts", which succeeds via mDNS even when
nothing was written — and accepted the fe80:: our own code rejects. It now
re-reads what was written.
awk replaces sed for filtering: "print" emits a newline, so an /etc/hosts with
no final one — cloud-init omits it — gets normalised. Without that our line
glued onto the previous one and the node's name pointed at another machine's
address.
Three more of the same kind. pmxcfs's dependents are restarted too: active
throughout the outage, they failed on ipcc_send_rec, and leaving them gave a
GUI in "communication failure" right after our ✓. A silent link is no longer
read as a missing mount. And the address is only blamed if pve-cluster
actually started.
The tests execute the commands instead of reading them, with stubs able to
FAIL: refused write, file with no final newline, tabs, a start that fails, a
mount that vanishes during reconfirmation. Proven by mutation — three
HOSTS-KO turned into HOSTS-OK make the test go red.
Assisted-by: Claude Opus 5
(cherry picked from commit d4f9358c6cb562029cc2ca9eb478d80c6a0a19a4)
2026-08-26 03:19:58 -04:00
|
|
|
|
# lignes n'ont plus d'endroit où aller, l'écran ne dirait que
|
|
|
|
|
|
# « aucun stockage », et surtout il ne pourrait pas POSER la
|
|
|
|
|
|
# question — le terminal est encore à nous ici.
|
|
|
|
|
|
#
|
|
|
|
|
|
# UNE sonde, passée aux deux : sondé deux fois, on annonçait une
|
|
|
|
|
|
# réparation que la seconde lecture refusait ensuite d'offrir.
|
|
|
|
|
|
etat_pve, quoi_pve = self._pve_cluster_state(host)
|
|
|
|
|
|
for ligne in self._pve_cluster_reason(host, etat_pve, quoi_pve):
|
[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
|
|
|
|
print(f" {ligne}")
|
[ADD] proxmox : l'écran remet pmxcfs debout lui-même
Le conseil « rejouer install_proxmox.sh sur l'hôte » ne pouvait PAS marcher :
la VM clone le dépôt distant, donc sa copie du script est celle du distant —
tant que le correctif n'y est pas, celle qui ne corrige rien. Trois hôtes de
suite sont tombés dessus, avec le même message inutile. L'écran répare donc :
gel de cloud-init, réécriture de /etc/hosts, relance des unités, constat du
montage — le pendant exact de l'offre de créer un pont.
Écrit, puis ATTAQUÉ par trois lentilles sur le code réel. Ce qu'elles ont
mesuré valait la peine.
/etc/hosts se réécrivait en DEUX écritures — « sed -i » puis « printf >> » —
alors que la docstring promettait l'inverse. Sed refusé et ajout réussi, la
ligne 127.0.1.1 survivait EN PREMIER et la nôtre s'ajoutait une fois par
tentative ; sed réussi et ajout refusé, l'hôte perdait l'entrée de son nom, et
chaque sudo y attendait ensuite le résolveur. C'est maintenant un fichier
complet bâti dans un temporaire, VÉRIFIÉ, puis recopié — « cat > » et non
« mv », qui remplacerait l'inode et perdrait mode et propriétaire.
Le contrôle final s'en remettait à « getent hosts », qui réussit via mDNS même
quand rien n'a été écrit — et acceptait les fe80:: que notre propre code
rejette. Il relit désormais ce qui a été écrit.
awk remplace sed pour filtrer : « print » émet un saut de ligne, donc un
/etc/hosts non terminé par un — cloud-init n'en met pas — est normalisé. Sans
ça notre ligne se collait à la précédente et le nom du nœud partait sur
l'adresse d'une autre machine.
Trois autres, du même acabit. Les dépendants de pmxcfs sont relancés eux
aussi : actifs pendant la panne, ils échouaient sur ipcc_send_rec, et les
laisser donnait une GUI en « communication failure » juste après notre ✓. Un
silence du lien n'est plus lu comme une absence de montage. Et l'adresse n'est
mise en cause que si pve-cluster a réellement démarré.
Les tests exécutent les commandes au lieu de les relire, bouchons capables
d'ÉCHOUER : écriture refusée, fichier sans saut de ligne final, tabulations,
start qui rate, montage qui disparaît pendant la reconfirmation. Prouvé par
mutation — trois HOSTS-KO changés en HOSTS-OK font rougir le test.
--- EN ---
The advice "replay install_proxmox.sh on the host" could NOT work: the VM
clones the remote, so its copy of the script is the remote's — while the fix
is not there, the one that fixes nothing. Three hosts in a row hit it with the
same useless message. So the screen repairs: freeze cloud-init, rewrite
/etc/hosts, restart the units, verify the mount — the exact counterpart of the
offer to create a bridge.
Written, then ATTACKED by three lenses on the real code. What they measured
was worth it.
/etc/hosts was rewritten in TWO writes — "sed -i" then "printf >>" — while the
docstring promised the opposite. Sed refused and append succeeded: the
127.0.1.1 line survived FIRST and ours was added once per attempt; sed
succeeded and append refused: the host lost its own name entry, and every sudo
then waited on the resolver. It is now a complete file built in a temporary,
VERIFIED, then copied over — "cat >" not "mv", which would replace the inode
and lose mode and owner.
The final check relied on "getent hosts", which succeeds via mDNS even when
nothing was written — and accepted the fe80:: our own code rejects. It now
re-reads what was written.
awk replaces sed for filtering: "print" emits a newline, so an /etc/hosts with
no final one — cloud-init omits it — gets normalised. Without that our line
glued onto the previous one and the node's name pointed at another machine's
address.
Three more of the same kind. pmxcfs's dependents are restarted too: active
throughout the outage, they failed on ipcc_send_rec, and leaving them gave a
GUI in "communication failure" right after our ✓. A silent link is no longer
read as a missing mount. And the address is only blamed if pve-cluster
actually started.
The tests execute the commands instead of reading them, with stubs able to
FAIL: refused write, file with no final newline, tabs, a start that fails, a
mount that vanishes during reconfirmation. Proven by mutation — three
HOSTS-KO turned into HOSTS-OK make the test go red.
Assisted-by: Claude Opus 5
(cherry picked from commit d4f9358c6cb562029cc2ca9eb478d80c6a0a19a4)
2026-08-26 03:19:58 -04:00
|
|
|
|
if self._pve_offer_cluster_fix(host, etat_pve, quoi_pve):
|
|
|
|
|
|
_c, out = self._pve_show(
|
|
|
|
|
|
"pvesm status --content images", quiet=True
|
|
|
|
|
|
)
|
|
|
|
|
|
stockages = pve.parse_storages(out)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
_c, out = self._pve_show("ip -o link show type bridge", quiet=True)
|
|
|
|
|
|
ponts = pve.parse_bridges(out)
|
[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
|
|
|
|
if not ponts:
|
|
|
|
|
|
# Le terminal est encore à nous : c'est ICI qu'on peut poser la
|
|
|
|
|
|
# question. L'écran sait aussi le faire, mais sans pouvoir
|
|
|
|
|
|
# expliquer les deux voies ni montrer ce qu'il exécute.
|
|
|
|
|
|
if self._pve_offer_bridge():
|
|
|
|
|
|
_c, out = self._pve_show(
|
|
|
|
|
|
"ip -o link show type bridge", quiet=True
|
|
|
|
|
|
)
|
|
|
|
|
|
ponts = pve.parse_bridges(out)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
_c, cfg = self._pve_show("cat /etc/network/interfaces", quiet=True)
|
|
|
|
|
|
infos = pve.parse_bridge_config(cfg)
|
|
|
|
|
|
cpu, ram_libre = self._pve_capacity()
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
|
# La profondeur borne ce que l'écran offre. Ici, terminal encore à
|
|
|
|
|
|
# nous : une fois Textual à l'affiche, ces lignes n'auraient nulle
|
|
|
|
|
|
# part où aller.
|
|
|
|
|
|
cpu, notes_profondeur = self._pve_depth_note(host, cpu)
|
|
|
|
|
|
for ligne in notes_profondeur:
|
|
|
|
|
|
print(f" {ligne}")
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
# Le DNS de l'hôte, pour les VM en adresse fixe : sans lui elles
|
|
|
|
|
|
# routent mais ne résolvent rien, et « apt update » échoue sans que
|
|
|
|
|
|
# rien ne l'explique. Mesuré sur la VM d'essai.
|
|
|
|
|
|
_c, resolv = self._pve_show(pve.RESOLV_CMD, quiet=True)
|
|
|
|
|
|
serveurs_dns = pve.parse_nameservers(resolv)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
|
|
|
|
|
|
def ipconfig(pont, vmid):
|
|
|
|
|
|
return pve.ipconfig_for(infos.get(pont, {}), vmid)
|
|
|
|
|
|
|
|
|
|
|
|
def build_command(vm, spec):
|
|
|
|
|
|
"""Les commandes qui seraient lancées pour CETTE VM."""
|
|
|
|
|
|
return self._pve_vm_commands(mod, vm, spec)
|
|
|
|
|
|
|
[ADD] proxmox deploy : accélération 3D, bibliothèques sondées et posées
A Proxmox VM always used the serial console as its display, so nothing inside
it could render. The form offers 3D: the VM gets « --vga virtio-gl » and the
account joins the render and video groups in the guest, which « qm set »
cannot write; without them GL falls back to software while VIRGL reports
success. The box is offered only where the host has a render node, VIRGL, GL
and EGL, Proxmox refusing to start the VM otherwise once its disk is written.
What is missing is named with its package, and a button installs it, then
reads the host again. Covered by test/test_proxmox_form.py.
--- FR ---
Une VM Proxmox prenait toujours la console série pour affichage : rien ne
pouvait y rendre. Le formulaire offre la 3D : la VM reçoit « --vga virtio-gl »
et le compte entre dans les groupes render et video dans l'invité, ce que
« qm set » ne sait pas écrire ; sans eux, GL retombe en logiciel alors que
VIRGL annonce un succès. La case n'est offerte que si l'hôte a un nœud de
rendu, VIRGL, GL et EGL, Proxmox refusant sinon de démarrer la VM une fois son
disque écrit. Ce qui manque est nommé avec son paquet, et un bouton le pose
puis relit l'hôte. Couvert par test/test_proxmox_form.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:20 -04:00
|
|
|
|
# UNE sonde, deux réponses : ce que l'hôte peut faire, et ce qui lui
|
|
|
|
|
|
# manque pour le faire. Sondé deux fois, l'écran pourrait offrir la
|
|
|
|
|
|
# case et nommer en même temps ce qui l'empêche.
|
|
|
|
|
|
gpu_possible, gpu_manque = self._pve_gpu_dispo()
|
|
|
|
|
|
|
|
|
|
|
|
def poser_gpu(paquets):
|
|
|
|
|
|
"""Pose les paquets manquants SUR L'HÔTE. Rend True si apt a fini.
|
|
|
|
|
|
|
|
|
|
|
|
Interactive à dessein : le terminal est rendu par l'écran avant
|
|
|
|
|
|
l'appel, « ssh -t » ouvre un vrai terminal distant, et sudo peut
|
|
|
|
|
|
donc demander son mot de passe. Rien n'est capturé — l'opérateur
|
|
|
|
|
|
voit apt travailler, ce qui est la moitié de la confiance.
|
|
|
|
|
|
|
|
|
|
|
|
Le nœud de rendu n'est pas un paquet et ne s'installe pas : il
|
|
|
|
|
|
est écarté, et une liste qui n'en contient pas d'autre ne lance
|
|
|
|
|
|
rien plutôt que d'appeler « apt install » les mains vides.
|
|
|
|
|
|
"""
|
|
|
|
|
|
noms = [
|
|
|
|
|
|
p
|
|
|
|
|
|
for p in str(paquets or "").split()
|
|
|
|
|
|
if p != "noeud" and re.fullmatch(r"[A-Za-z0-9.+_-]+", p)
|
|
|
|
|
|
]
|
|
|
|
|
|
if not noms:
|
|
|
|
|
|
return False
|
|
|
|
|
|
remote = pve.wrap_privilege(
|
|
|
|
|
|
"apt-get update && apt-get install -y " + " ".join(noms),
|
|
|
|
|
|
host.get("sudo") or "",
|
|
|
|
|
|
)
|
|
|
|
|
|
argv = pve.ssh_argv(host, remote, tty=True)
|
|
|
|
|
|
print("\n" + " ".join(shlex.quote(a) for a in argv) + "\n")
|
|
|
|
|
|
return subprocess.call(argv) == 0
|
|
|
|
|
|
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
return {
|
|
|
|
|
|
"host": dict(host, label=self._pve_label(host)),
|
|
|
|
|
|
"node": self._pve_node_name(),
|
|
|
|
|
|
"catalog": catalog,
|
|
|
|
|
|
"arches": arches,
|
|
|
|
|
|
"native": native,
|
|
|
|
|
|
"names": [v["name"] for v in vms if v.get("name")],
|
|
|
|
|
|
"vmids": [v["vmid"] for v in vms],
|
|
|
|
|
|
"next_vmid": pve.next_vmid(vms),
|
|
|
|
|
|
"storages": [s["name"] for s in stockages if s.get("actif")],
|
|
|
|
|
|
"storage": pve.pick_storage(stockages),
|
[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
|
|
|
|
# La place libre par stockage, en octets : « pvesm status » la
|
|
|
|
|
|
# donne dans la même sortie, donc l'écran peut dire si le plan
|
|
|
|
|
|
# rentre sans un aller-retour de plus vers l'hôte.
|
|
|
|
|
|
"storage_avail": {
|
|
|
|
|
|
s["name"]: s.get("avail") or 0 for s in stockages
|
|
|
|
|
|
},
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
"bridges": ponts,
|
|
|
|
|
|
"bridge": pve.pick_bridge(ponts),
|
|
|
|
|
|
"ipconfig": ipconfig,
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
"nameservers": serveurs_dns,
|
[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
|
|
|
|
# De quoi créer le pont DEPUIS l'écran, sans invite : le pont
|
|
|
|
|
|
# interne ne touche à aucune interface physique.
|
|
|
|
|
|
"make_bridge": self._pve_make_internal_bridge,
|
[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
|
|
|
|
# Le libellé « ➕ créer un interne vmbr0 (…) » doit annoncer le
|
|
|
|
|
|
# réseau qui sera RÉELLEMENT posé — il dépend de l'hôte.
|
|
|
|
|
|
"internal_bridge": (
|
|
|
|
|
|
pve.INTERNAL_BRIDGE,
|
|
|
|
|
|
(self._pve_internal_cidr(host) if not ponts else "")
|
|
|
|
|
|
or pve.INTERNAL_CIDR,
|
|
|
|
|
|
),
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
"build_command": build_command,
|
|
|
|
|
|
"branches": self._qemu_branch_list() or ["master"],
|
2026-08-24 05:38:28 -04:00
|
|
|
|
# La branche du dépôt : c'est elle qu'on déploie le plus souvent.
|
|
|
|
|
|
"branch_current": self._qemu_repo_branch(),
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
"install_profiles": self._qemu_install_profiles(),
|
[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
|
|
|
|
# Type de VM, magasin d'applications, outils, fuseau,
|
|
|
|
|
|
# interpréteur Python : les réglages du système INVITÉ, qui ne
|
|
|
|
|
|
# regardent pas l'hyperviseur. Cet écran n'en portait que trois —
|
|
|
|
|
|
# une VM créée ici naissait sans bureau, sans outils et en UTC.
|
|
|
|
|
|
**self._qemu_guest_context(),
|
[FIX] qemu : ne pas poser ERPLibre sur une VM Proxmox VE
Choisir « Proxmox VE » comme système, c'est demander qu'il soit installé.
L'invite en ligne le savait ; le formulaire posait « ERPLibre + Odoo 18 »,
lui ajoutait les 5 Go réservés au dépôt et annonçait une cible make dans le
guide de la VM. La règle vit maintenant en un seul endroit, les deux
chemins la lisent, et un choix explicite l'emporte toujours sur elle.
Trois défauts trouvés derrière. Une commande par VM n'était retenue que si
DEUX VM différaient : déployée seule, la VM Proxmox retombait sur la
commande commune. Le disque choisi à la main disparaissait quand rien
n'était installé — 60 G demandés, 20 G créés. Et une taille absente des
préréglages marquait la rangée ✎ dès son montage.
--- EN ---
Choosing "Proxmox VE" as the system means asking for it to be installed.
The command-line prompt knew that; the form set "ERPLibre + Odoo 18", added
the 5 GB meant for the repository and advertised a make target in the VM's
guide. The rule now lives in one place, both paths read it, and an explicit
choice always wins over it.
Three defects behind it. A per-VM command was only used when TWO VMs
differed: deployed alone, the Proxmox VM fell back to the common command.
A hand-picked disk size vanished when nothing was installed — 60 G asked,
20 G created. And a size absent from the presets marked its row ✎ on mount.
Assisted-by: Claude Opus 5
2026-08-24 01:34:10 -04:00
|
|
|
|
# Même règle qu'en QEMU/KVM : un système peut IMPOSER ce qu'on
|
|
|
|
|
|
# installe dessus. Un Proxmox imbriqué recevait sinon ERPLibre et
|
|
|
|
|
|
# Odoo 18, comme l'écran d'à côté avant correction.
|
|
|
|
|
|
"distro_profiles": {
|
|
|
|
|
|
d: self._qemu_distro_profile(d)
|
|
|
|
|
|
for d in self._QEMU_DISTRO_PROFILE
|
|
|
|
|
|
if self._qemu_distro_profile(d)
|
|
|
|
|
|
},
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
"ssh_key": self._qemu_default_ssh_key(),
|
|
|
|
|
|
"cpu_presets": self._QEMU_CPU_PRESETS,
|
|
|
|
|
|
"ram_presets": self._QEMU_RAM_PRESETS,
|
|
|
|
|
|
"disk_presets": self._QEMU_DISK_PRESETS,
|
|
|
|
|
|
"base_vcpus": self._QEMU_BASE_VCPUS,
|
|
|
|
|
|
# Les cœurs et la mémoire de L'HÔTE DISTANT : ceux d'ici ne
|
|
|
|
|
|
# disent rien de ce qu'on peut y loger.
|
|
|
|
|
|
"host_cpu": cpu,
|
|
|
|
|
|
"free_ram": ram_libre,
|
|
|
|
|
|
"extra_disk_gb": self.ERPLIBRE_EXTRA_DISK_GB,
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
# La case « Sans connexion internet » ne s'offre que là où la
|
|
|
|
|
|
# coupure a un effet. Un hôte Proxmox qui reçoit l'autorité du
|
|
|
|
|
|
# cache est une VM de CE pont : ses invités sortent derrière son
|
|
|
|
|
|
# adresse, donc la coupure de ce pont les couvre. Un hôte qui ne
|
|
|
|
|
|
# vit pas ici ne traverse rien qu'on sache couper, et la case y
|
|
|
|
|
|
# promettrait un hors-ligne que personne ne tient.
|
|
|
|
|
|
"cache_offert": bool(self._pve_cache_ca(host)),
|
[ADD] proxmox deploy : accélération 3D, bibliothèques sondées et posées
A Proxmox VM always used the serial console as its display, so nothing inside
it could render. The form offers 3D: the VM gets « --vga virtio-gl » and the
account joins the render and video groups in the guest, which « qm set »
cannot write; without them GL falls back to software while VIRGL reports
success. The box is offered only where the host has a render node, VIRGL, GL
and EGL, Proxmox refusing to start the VM otherwise once its disk is written.
What is missing is named with its package, and a button installs it, then
reads the host again. Covered by test/test_proxmox_form.py.
--- FR ---
Une VM Proxmox prenait toujours la console série pour affichage : rien ne
pouvait y rendre. Le formulaire offre la 3D : la VM reçoit « --vga virtio-gl »
et le compte entre dans les groupes render et video dans l'invité, ce que
« qm set » ne sait pas écrire ; sans eux, GL retombe en logiciel alors que
VIRGL annonce un succès. La case n'est offerte que si l'hôte a un nœud de
rendu, VIRGL, GL et EGL, Proxmox refusant sinon de démarrer la VM une fois son
disque écrit. Ce qui manque est nommé avec son paquet, et un bouton le pose
puis relit l'hôte. Couvert par test/test_proxmox_form.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:20 -04:00
|
|
|
|
# Lu ICI, terminal encore à nous : la sonde passe par ssh, et une
|
|
|
|
|
|
# invite de mot de passe pendant que l'écran affiche le casserait.
|
|
|
|
|
|
"gpu_offert": gpu_possible,
|
|
|
|
|
|
# Ce qui manque, nommé : une case qui disparaît sans un mot se
|
|
|
|
|
|
# lit comme une régression, et l'opérateur n'a rien à corriger.
|
|
|
|
|
|
"gpu_manque": gpu_manque,
|
|
|
|
|
|
# De quoi poser ce qui manque sans quitter l'écran, et de quoi
|
|
|
|
|
|
# RELIRE l'hôte ensuite : sans la seconde, l'écran croirait sur
|
|
|
|
|
|
# parole qu'apt a réussi.
|
|
|
|
|
|
"installer_gpu": poser_gpu,
|
|
|
|
|
|
"sonder_gpu": self._pve_gpu_dispo,
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_capacity(self):
|
|
|
|
|
|
"""(cœurs, Mo de RAM libre) de l'hôte, ou un repli prudent.
|
|
|
|
|
|
|
|
|
|
|
|
Deux valeurs en une commande : le formulaire s'en sert pour borner les
|
|
|
|
|
|
vCPU et pour prévenir quand le plan demande plus de mémoire que
|
|
|
|
|
|
l'hôte n'en a de libre."""
|
|
|
|
|
|
code, out = self._pve_show(
|
|
|
|
|
|
"nproc; free -m | awk '/^Mem:/ {print $7}'", quiet=True
|
|
|
|
|
|
)
|
|
|
|
|
|
lignes = [x.strip() for x in (out or "").splitlines() if x.strip()]
|
|
|
|
|
|
if code or len(lignes) < 2:
|
|
|
|
|
|
return 2, 0
|
|
|
|
|
|
cpu = int(lignes[0]) if lignes[0].isdigit() else 2
|
|
|
|
|
|
libre = int(lignes[1]) if lignes[1].isdigit() else 0
|
|
|
|
|
|
return cpu, libre
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_node_name(self):
|
|
|
|
|
|
"""Nom du nœud Proxmox, tel qu'il se nomme lui-même."""
|
|
|
|
|
|
code, out = self._pve_show("hostname", quiet=True)
|
|
|
|
|
|
return out.strip().splitlines()[0] if code == 0 and out.strip() else ""
|
|
|
|
|
|
|
[FIX] proxmox : six défauts trouvés par un audit, pas à l'usage
Trois autres chemins menaient au 🗑 sur un seul incident, et « effacée » gèle
la ligne pour de bon. Un « virsh list » en échec condamnait TOUT le parc
local. Un statut Proxmox hors des trois attendus — prelaunch, suspended,
internal-error — passait pour une disparition. Et le code de sortie de la
suite distante est celui de son DERNIER maillon : un pvesh en panne se lisait
« l'hôte a répondu sans elle ». Ce qui prouve une réponse, c'est désormais une
liste de ressources analysable.
Le plan annonçait « 25G » quand « qm resize » recevait 20 : la marge
d'ERPLibre se perdait en route, la VM naissait trop petite. « Changer l'état »
choisissait par NOM, or seul le VMID est unique sur un hôte — cocher une VM
en éteignait deux homonymes. L'entrée 13 volait son alias à une VM locale du
même nom. Enfin le déploiement par QUESTIONS avait vieilli seul : il partage
maintenant l'épilogue de l'écran, donc le guide, l'alias protégé, les colonnes
vivantes et le sommaire.
--- EN ---
Three more paths led to 🗑 on a single incident, and "deleted" freezes the row
for good. One failing "virsh list" condemned the WHOLE local fleet. A Proxmox
status outside the three expected ones — prelaunch, suspended, internal-error
— passed for a disappearance. And a remote pipeline's exit code is its LAST
link's: a broken pvesh read as "the host answered without it". Proof of an
answer is now a parsable resource list.
The plan announced "25G" while "qm resize" got 20: ERPLibre's margin was lost
on the way and the VM was born too small. "Change state" selected by NAME,
yet only the VMID is unique on a host — ticking one VM shut down two
namesakes. Menu entry 13 stole its alias from a local VM of the same name.
Finally the QUESTION-driven deployment had aged alone: it now shares the
screen's epilogue — guide, protected alias, live columns and summary.
Assisted-by: Claude Opus 5
2026-08-24 14:15:03 -04:00
|
|
|
|
def _pve_disk_with_margin(self, vm, spec):
|
|
|
|
|
|
"""Taille du disque à créer : celle du plan, marge comprise.
|
|
|
|
|
|
|
|
|
|
|
|
La même règle que la voie libvirt, qui ajoute ERPLIBRE_EXTRA_DISK_GB à
|
|
|
|
|
|
la demande initiale quand ERPLibre s'installe. Ici la marge se perdait
|
|
|
|
|
|
entre l'écran et « qm resize ».
|
|
|
|
|
|
"""
|
[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
|
|
|
|
from script.todo.deploy_form_extras import (
|
|
|
|
|
|
extras_disk_gb,
|
|
|
|
|
|
extras_tables,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
[FIX] proxmox : six défauts trouvés par un audit, pas à l'usage
Trois autres chemins menaient au 🗑 sur un seul incident, et « effacée » gèle
la ligne pour de bon. Un « virsh list » en échec condamnait TOUT le parc
local. Un statut Proxmox hors des trois attendus — prelaunch, suspended,
internal-error — passait pour une disparition. Et le code de sortie de la
suite distante est celui de son DERNIER maillon : un pvesh en panne se lisait
« l'hôte a répondu sans elle ». Ce qui prouve une réponse, c'est désormais une
liste de ressources analysable.
Le plan annonçait « 25G » quand « qm resize » recevait 20 : la marge
d'ERPLibre se perdait en route, la VM naissait trop petite. « Changer l'état »
choisissait par NOM, or seul le VMID est unique sur un hôte — cocher une VM
en éteignait deux homonymes. L'entrée 13 volait son alias à une VM locale du
même nom. Enfin le déploiement par QUESTIONS avait vieilli seul : il partage
maintenant l'épilogue de l'écran, donc le guide, l'alias protégé, les colonnes
vivantes et le sommaire.
--- EN ---
Three more paths led to 🗑 on a single incident, and "deleted" freezes the row
for good. One failing "virsh list" condemned the WHOLE local fleet. A Proxmox
status outside the three expected ones — prelaunch, suspended, internal-error
— passed for a disappearance. And a remote pipeline's exit code is its LAST
link's: a broken pvesh read as "the host answered without it". Proof of an
answer is now a parsable resource list.
The plan announced "25G" while "qm resize" got 20: ERPLibre's margin was lost
on the way and the VM was born too small. "Change state" selected by NAME,
yet only the VMID is unique on a host — ticking one VM shut down two
namesakes. Menu entry 13 stole its alias from a local VM of the same name.
Finally the QUESTION-driven deployment had aged alone: it now shares the
screen's epilogue — guide, protected alias, live columns and summary.
Assisted-by: Claude Opus 5
2026-08-24 14:15:03 -04:00
|
|
|
|
demande = vm.get("disk") or ""
|
|
|
|
|
|
gigs = self._parse_disk_gb(demande)
|
|
|
|
|
|
if not gigs:
|
|
|
|
|
|
return demande
|
[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
|
|
|
|
install = spec.get("install") or {}
|
|
|
|
|
|
cmd = vm.get("install_cmd") or install.get("cmd") or ""
|
|
|
|
|
|
marge = 0
|
|
|
|
|
|
if self._qemu_installs_erplibre(install.get("branch"), cmd):
|
|
|
|
|
|
marge += self.ERPLIBRE_EXTRA_DISK_GB
|
|
|
|
|
|
# Le bureau et les outils pèsent aussi, et sur la VM QUI LES REÇOIT :
|
|
|
|
|
|
# une VM ARM n'aura pas Android Studio, un serveur aucun des IDE. Le
|
|
|
|
|
|
# plan les additionne déjà à l'écran ; sans eux ici, la VM naissait
|
|
|
|
|
|
# avec le disque d'un serveur nu et GNOME le remplissait.
|
|
|
|
|
|
marge += extras_disk_gb(
|
|
|
|
|
|
dict(vm, desktop=vm.get("desktop") or spec.get("desktop") or ""),
|
|
|
|
|
|
spec.get("vm_tools") or (),
|
|
|
|
|
|
extras_tables(self._qemu_guest_context()),
|
|
|
|
|
|
)
|
|
|
|
|
|
return f"{gigs + marge}G" if marge else demande
|
[FIX] proxmox : six défauts trouvés par un audit, pas à l'usage
Trois autres chemins menaient au 🗑 sur un seul incident, et « effacée » gèle
la ligne pour de bon. Un « virsh list » en échec condamnait TOUT le parc
local. Un statut Proxmox hors des trois attendus — prelaunch, suspended,
internal-error — passait pour une disparition. Et le code de sortie de la
suite distante est celui de son DERNIER maillon : un pvesh en panne se lisait
« l'hôte a répondu sans elle ». Ce qui prouve une réponse, c'est désormais une
liste de ressources analysable.
Le plan annonçait « 25G » quand « qm resize » recevait 20 : la marge
d'ERPLibre se perdait en route, la VM naissait trop petite. « Changer l'état »
choisissait par NOM, or seul le VMID est unique sur un hôte — cocher une VM
en éteignait deux homonymes. L'entrée 13 volait son alias à une VM locale du
même nom. Enfin le déploiement par QUESTIONS avait vieilli seul : il partage
maintenant l'épilogue de l'écran, donc le guide, l'alias protégé, les colonnes
vivantes et le sommaire.
--- EN ---
Three more paths led to 🗑 on a single incident, and "deleted" freezes the row
for good. One failing "virsh list" condemned the WHOLE local fleet. A Proxmox
status outside the three expected ones — prelaunch, suspended, internal-error
— passed for a disappearance. And a remote pipeline's exit code is its LAST
link's: a broken pvesh read as "the host answered without it". Proof of an
answer is now a parsable resource list.
The plan announced "25G" while "qm resize" got 20: ERPLibre's margin was lost
on the way and the VM was born too small. "Change state" selected by NAME,
yet only the VMID is unique on a host — ticking one VM shut down two
namesakes. Menu entry 13 stole its alias from a local VM of the same name.
Finally the QUESTION-driven deployment had aged alone: it now shares the
screen's epilogue — guide, protected alias, live columns and summary.
Assisted-by: Claude Opus 5
2026-08-24 14:15:03 -04:00
|
|
|
|
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
def _pve_vm_commands(self, mod, vm, spec):
|
|
|
|
|
|
"""Les commandes de création d'UNE VM, dans l'ordre : l'image puis
|
|
|
|
|
|
« qm ». Sert à l'aperçu comme à l'exécution — un aperçu qui ne
|
|
|
|
|
|
montrerait pas exactement ce qui va tourner ne servirait à rien."""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
code, _v = mod.DISTROS[vm["distro"]][0][vm["version"]][:2]
|
|
|
|
|
|
url = mod.image_url(vm["distro"], code, vm["arch"], vm["version"])
|
|
|
|
|
|
image = mod.default_image_name(
|
|
|
|
|
|
vm["distro"], code, vm["arch"], vm["version"]
|
|
|
|
|
|
)
|
|
|
|
|
|
detail = {
|
|
|
|
|
|
"name": vm["name"],
|
|
|
|
|
|
"memory": vm["ram"],
|
|
|
|
|
|
"vcpus": vm["vcpus"],
|
[FIX] proxmox : six défauts trouvés par un audit, pas à l'usage
Trois autres chemins menaient au 🗑 sur un seul incident, et « effacée » gèle
la ligne pour de bon. Un « virsh list » en échec condamnait TOUT le parc
local. Un statut Proxmox hors des trois attendus — prelaunch, suspended,
internal-error — passait pour une disparition. Et le code de sortie de la
suite distante est celui de son DERNIER maillon : un pvesh en panne se lisait
« l'hôte a répondu sans elle ». Ce qui prouve une réponse, c'est désormais une
liste de ressources analysable.
Le plan annonçait « 25G » quand « qm resize » recevait 20 : la marge
d'ERPLibre se perdait en route, la VM naissait trop petite. « Changer l'état »
choisissait par NOM, or seul le VMID est unique sur un hôte — cocher une VM
en éteignait deux homonymes. L'entrée 13 volait son alias à une VM locale du
même nom. Enfin le déploiement par QUESTIONS avait vieilli seul : il partage
maintenant l'épilogue de l'écran, donc le guide, l'alias protégé, les colonnes
vivantes et le sommaire.
--- EN ---
Three more paths led to 🗑 on a single incident, and "deleted" freezes the row
for good. One failing "virsh list" condemned the WHOLE local fleet. A Proxmox
status outside the three expected ones — prelaunch, suspended, internal-error
— passed for a disappearance. And a remote pipeline's exit code is its LAST
link's: a broken pvesh read as "the host answered without it". Proof of an
answer is now a parsable resource list.
The plan announced "25G" while "qm resize" got 20: ERPLibre's margin was lost
on the way and the VM was born too small. "Change state" selected by NAME,
yet only the VMID is unique on a host — ticking one VM shut down two
namesakes. Menu entry 13 stole its alias from a local VM of the same name.
Finally the QUESTION-driven deployment had aged alone: it now shares the
screen's epilogue — guide, protected alias, live columns and summary.
Assisted-by: Claude Opus 5
2026-08-24 14:15:03 -04:00
|
|
|
|
# La MARGE d'ERPLibre entre dans la taille réellement créée : le
|
|
|
|
|
|
# plan l'annonçait (« 25G » pour un catalogue à 20 G) et « qm
|
|
|
|
|
|
# resize » recevait 20 G. La VM naissait cinq gigaoctets trop
|
|
|
|
|
|
# petite pour ce qu'on venait de lui promettre — trouvé par
|
|
|
|
|
|
# l'audit, pas à l'usage.
|
|
|
|
|
|
"disk": self._pve_disk_with_margin(vm, spec),
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
"storage": spec["storage"],
|
|
|
|
|
|
"bridge": spec["bridge"],
|
|
|
|
|
|
"image": image,
|
2026-09-16 00:42:31 -04:00
|
|
|
|
# L'image qui n'a pas de secteur d'amorçage BIOS : le catalogue le
|
|
|
|
|
|
# sait, ce menu le transmet, et « qm create » en tire l'OVMF.
|
|
|
|
|
|
"uefi": mod.requiert_uefi(vm.get("distro") or ""),
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
"user": spec.get("user") or "erplibre",
|
|
|
|
|
|
"start": spec.get("start", True),
|
|
|
|
|
|
"ipconfig": vm.get("ipconfig") or "ip=dhcp",
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
# Le DNS de l'hôte : « --ipconfig0 » ne le porte pas, et une VM
|
|
|
|
|
|
# en adresse fixe se retrouvait sans résolveur.
|
|
|
|
|
|
"nameservers": spec.get("nameservers") or (),
|
[ADD] proxmox deploy : accélération 3D, bibliothèques sondées et posées
A Proxmox VM always used the serial console as its display, so nothing inside
it could render. The form offers 3D: the VM gets « --vga virtio-gl » and the
account joins the render and video groups in the guest, which « qm set »
cannot write; without them GL falls back to software while VIRGL reports
success. The box is offered only where the host has a render node, VIRGL, GL
and EGL, Proxmox refusing to start the VM otherwise once its disk is written.
What is missing is named with its package, and a button installs it, then
reads the host again. Covered by test/test_proxmox_form.py.
--- FR ---
Une VM Proxmox prenait toujours la console série pour affichage : rien ne
pouvait y rendre. Le formulaire offre la 3D : la VM reçoit « --vga virtio-gl »
et le compte entre dans les groupes render et video dans l'invité, ce que
« qm set » ne sait pas écrire ; sans eux, GL retombe en logiciel alors que
VIRGL annonce un succès. La case n'est offerte que si l'hôte a un nœud de
rendu, VIRGL, GL et EGL, Proxmox refusant sinon de démarrer la VM une fois son
disque écrit. Ce qui manque est nommé avec son paquet, et un bouton le pose
puis relit l'hôte. Couvert par test/test_proxmox_form.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:20 -04:00
|
|
|
|
# L'accélération 3D se décide à la CRÉATION : l'écran d'une VM
|
|
|
|
|
|
# Proxmox est un choix de « qm create », et le changer ensuite
|
|
|
|
|
|
# demande de l'éteindre.
|
|
|
|
|
|
"gpu3d": bool(spec.get("gpu3d")),
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
}
|
|
|
|
|
|
if spec.get("sshkey_path"):
|
|
|
|
|
|
detail["sshkey_path"] = spec["sshkey_path"]
|
2026-09-16 00:42:31 -04:00
|
|
|
|
# Le user-data du dépôt, pour TOUTES les distributions. La clé locale
|
|
|
|
|
|
# vient de la spec de l'écran ; sans elle, on retombe sur la forme
|
|
|
|
|
|
# d'avant, qui pose au moins un compte.
|
|
|
|
|
|
cle_locale = spec.get("ssh_key_local") or self._qemu_default_ssh_key()
|
|
|
|
|
|
if cle_locale:
|
|
|
|
|
|
detail["user_data"] = self._pve_user_data(
|
|
|
|
|
|
mod, vm.get("distro") or "", vm.get("name") or "", cle_locale
|
|
|
|
|
|
)
|
|
|
|
|
|
# La somme que le dépôt porte pour cette image, quand il en a une :
|
|
|
|
|
|
# c'est la seule chose qui distingue une image tierce revue de
|
|
|
|
|
|
# n'importe quel fichier servi sous la même URL.
|
|
|
|
|
|
somme = mod.pinned_sha256(vm.get("distro") or "")
|
|
|
|
|
|
return [
|
|
|
|
|
|
pve.image_fetch_cmd(url, image, sha256=somme)
|
|
|
|
|
|
] + pve.create_cmds(vm["vmid"], detail)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
def _pve_deploy_spec(self, host, spec, mod, dry_run=False, coupee=False):
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
"""Exécute la spec rendue par l'écran.
|
|
|
|
|
|
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
`coupee` : l'amont du cache est coupé autour de cet appel. La levée
|
|
|
|
|
|
est alors confiée au guet, au lancement des installations, comme sur
|
|
|
|
|
|
la voie QEMU/KVM — sans quoi elle tomberait avec ce processus, avant
|
|
|
|
|
|
la fin de ce qui télécharge.
|
|
|
|
|
|
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
Les images D'ABORD, une par une : deux téléchargements simultanés du
|
|
|
|
|
|
même fichier se marcheraient dessus. Les VM ensuite, en parallèle si
|
|
|
|
|
|
on l'a demandé — chacune est une suite « qm » indépendante.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
from script.todo.deploy_form_lib import run_deploy_progress
|
|
|
|
|
|
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
# L'instant où CE déploiement commence : le manifeste le porte, et le
|
|
|
|
|
|
# bilan hors ligne s'en sert pour ne relire que ce qui s'est passé
|
|
|
|
|
|
# depuis. Pris avant la première commande, création comprise.
|
|
|
|
|
|
debut = time.time()
|
|
|
|
|
|
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
# Le stockage et le pont AVANT tout : l'écran les vérifie déjà, mais
|
|
|
|
|
|
# cette méthode s'appelle aussi d'ailleurs. Sans ce garde-fou, on
|
|
|
|
|
|
# téléchargeait 350 Mio d'image pour finir sur « net0: invalid format
|
|
|
|
|
|
# - missing key » — vécu sur l'hôte d'essai.
|
|
|
|
|
|
for valeur, message in (
|
|
|
|
|
|
(spec.get("storage"), t("No storage able to hold a VM disk.")),
|
|
|
|
|
|
(spec.get("bridge"), t("No bridge on the host.")),
|
|
|
|
|
|
):
|
|
|
|
|
|
if not valeur:
|
|
|
|
|
|
print(f"\n ✗ {message}")
|
|
|
|
|
|
return
|
[ADD] proxmox : récapituler dans le terminal avant de créer
L'écran montre le plan, mais la création part sur une machine qui n'est pas
la nôtre, et l'hôte va demander sudo juste après. La ligne dit donc où,
quoi et combien — hôte, stockage, pont, puis une ligne par VM avec son VMID
et son adresse — et attend un oui. Le déploiement QEMU/KVM le fait déjà ;
c'était la seule étape qui manquait ici.
--- EN ---
The screen shows the plan, but creation goes to a machine that is not ours,
and the host will ask for sudo right after. So the recap says where, what
and how many — host, storage, bridge, then one line per VM with its VMID
and address — and waits for a yes. The QEMU/KVM deployment already does
this; it was the only step missing here.
Assisted-by: Claude Opus 5
2026-08-23 18:05:55 -04:00
|
|
|
|
if not dry_run and not self._pve_confirm_spec(host, spec):
|
|
|
|
|
|
print(t("Cancelled."))
|
|
|
|
|
|
return
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
cle_locale = spec.get("ssh_key") or self._qemu_default_ssh_key()
|
|
|
|
|
|
if cle_locale and not dry_run:
|
|
|
|
|
|
if self._pve_push_key(cle_locale):
|
|
|
|
|
|
spec["sshkey_path"] = "/root/.ssh/erplibre-deploy.pub"
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f" ⚠ {t('SSH key not pushed: password login only.')}")
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
travaux, commandes = [], {}
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
for vm in spec["vms"]:
|
|
|
|
|
|
cmds = self._pve_vm_commands(mod, vm, spec)
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
commandes[vm["name"]] = cmds
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
if dry_run:
|
|
|
|
|
|
print(f"\n── {vm['name']} ({t('VMID')} {vm['vmid']}) ──")
|
|
|
|
|
|
for cmd in cmds:
|
|
|
|
|
|
print(f" {cmd}")
|
|
|
|
|
|
continue
|
|
|
|
|
|
# UNE seule commande distante par VM : l'enchaînement par « && »
|
|
|
|
|
|
# s'arrête à la première étape qui cède, et le journal de la VM
|
|
|
|
|
|
# porte toute sa création.
|
|
|
|
|
|
remote = " && ".join(cmds)
|
|
|
|
|
|
travaux.append(
|
|
|
|
|
|
(
|
|
|
|
|
|
vm["name"],
|
|
|
|
|
|
vm["name"],
|
|
|
|
|
|
pve.ssh_argv(
|
|
|
|
|
|
host,
|
|
|
|
|
|
pve.wrap_privilege(remote, host.get("sudo") or ""),
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
|
|
|
|
|
if dry_run:
|
|
|
|
|
|
return
|
|
|
|
|
|
if spec["existing"]:
|
|
|
|
|
|
print(f" ⏭ {t('already there')} : {', '.join(spec['existing'])}")
|
[ADD] proxmox : récapituler dans le terminal avant de créer
L'écran montre le plan, mais la création part sur une machine qui n'est pas
la nôtre, et l'hôte va demander sudo juste après. La ligne dit donc où,
quoi et combien — hôte, stockage, pont, puis une ligne par VM avec son VMID
et son adresse — et attend un oui. Le déploiement QEMU/KVM le fait déjà ;
c'était la seule étape qui manquait ici.
--- EN ---
The screen shows the plan, but creation goes to a machine that is not ours,
and the host will ask for sudo right after. So the recap says where, what
and how many — host, storage, bridge, then one line per VM with its VMID
and address — and waits for a yes. The QEMU/KVM deployment already does
this; it was the only step missing here.
Assisted-by: Claude Opus 5
2026-08-23 18:05:55 -04:00
|
|
|
|
if not travaux:
|
|
|
|
|
|
return
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
# Le journal AVANT de lancer : la vue de progression se referme et
|
|
|
|
|
|
# emporte tout ce qu'elle montrait. Rapporté — « il manque plein
|
|
|
|
|
|
# d'informations qu'il y avait avant, où est le fichier de log ? ».
|
|
|
|
|
|
# L'ancienne voie par questions imprimait chaque commande et sa
|
|
|
|
|
|
# sortie ; celle-ci les écrit, ce qui vaut mieux qu'un défilement.
|
|
|
|
|
|
session = self._pve_log_dir()
|
|
|
|
|
|
print(f"\n {t('Log:')} {session}")
|
[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
|
|
|
|
# Comment joindre chaque VM SANS dépendre de ~/.ssh/config, qui n'est
|
|
|
|
|
|
# écrit qu'après : par le rebond de l'hôte, explicitement. C'est ce que
|
|
|
|
|
|
# « s » utilise dans la vue de progression — sans quoi il partait sur
|
|
|
|
|
|
# le nom de la VM, donc sur une locale homonyme (rapporté).
|
|
|
|
|
|
cibles_ssh = {}
|
|
|
|
|
|
for vm in spec["vms"]:
|
|
|
|
|
|
ip = pve.ip_from_ipconfig(vm.get("ipconfig") or "")
|
|
|
|
|
|
if ip:
|
|
|
|
|
|
compte = (spec.get("user") or "erplibre") + "@" + ip
|
|
|
|
|
|
cibles_ssh[vm["name"]] = (
|
|
|
|
|
|
f"ssh -J {shlex.quote(host['target'])} "
|
|
|
|
|
|
f"{shlex.quote(compte)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
# Ce qui attend derrière cet écran : sans le dire, on reste devant
|
|
|
|
|
|
# une fenêtre « terminée » sans savoir que l'installation d'ERPLibre
|
|
|
|
|
|
# démarre en la quittant.
|
|
|
|
|
|
suite = ""
|
|
|
|
|
|
if spec.get("install"):
|
|
|
|
|
|
suite = t("Quit (q) to start the ERPLibre install")
|
|
|
|
|
|
elif spec.get("monitor", True):
|
|
|
|
|
|
suite = t("Quit (q) to follow the VM starting up")
|
|
|
|
|
|
resultats = run_deploy_progress(
|
|
|
|
|
|
travaux,
|
|
|
|
|
|
spec.get("parallelism") or 1,
|
|
|
|
|
|
ssh_cmds=cibles_ssh,
|
|
|
|
|
|
suite=suite,
|
|
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
reussies = [nom for nom, code, _o, _d in resultats if code == 0]
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
for nom, code, sortie, duree in resultats:
|
|
|
|
|
|
chemin = self._pve_write_log(
|
|
|
|
|
|
session,
|
|
|
|
|
|
nom,
|
|
|
|
|
|
spec,
|
|
|
|
|
|
commandes.get(nom) or [],
|
|
|
|
|
|
code,
|
|
|
|
|
|
sortie,
|
|
|
|
|
|
duree,
|
|
|
|
|
|
)
|
|
|
|
|
|
marque = "✓" if code == 0 else "✗"
|
|
|
|
|
|
print(f" {marque} {nom} : {chemin}")
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
if code:
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
print(f" {t('exit code')} {code}")
|
|
|
|
|
|
# Les dernières lignes à l'écran, le reste dans le journal :
|
|
|
|
|
|
# c'est l'échec qu'on veut lire tout de suite.
|
|
|
|
|
|
propre = pve.collapse_progress(pve.strip_ssh_noise(sortie))
|
|
|
|
|
|
for ligne in propre.rstrip().splitlines()[-12:]:
|
|
|
|
|
|
print(f" {ligne}")
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
if not reussies:
|
|
|
|
|
|
return
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
joignables = self._pve_after_create(
|
|
|
|
|
|
host, spec, reussies, cle_locale, coupee=coupee, debut=debut
|
|
|
|
|
|
)
|
[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
|
|
|
|
self._pve_print_summary(spec, joignables or [], session)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _pve_log_dir():
|
|
|
|
|
|
"""Répertoire de journaux de CE déploiement, créé au besoin.
|
|
|
|
|
|
|
|
|
|
|
|
Même esprit que ~/.erplibre/qemu-install : une session par
|
|
|
|
|
|
déploiement, un fichier par VM. La vue de progression se referme ; le
|
|
|
|
|
|
journal reste, et c'est lui qu'on relit quand une étape a cédé."""
|
|
|
|
|
|
session = os.path.join(
|
|
|
|
|
|
os.path.expanduser("~/.erplibre/proxmox-deploy"),
|
|
|
|
|
|
time.strftime("%Y%m%d-%H%M%S"),
|
|
|
|
|
|
)
|
|
|
|
|
|
os.makedirs(session, exist_ok=True)
|
|
|
|
|
|
return session
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _pve_write_log(session, nom, spec, cmds, code, sortie, duree):
|
|
|
|
|
|
"""Écrit le journal d'UNE VM et rend son chemin.
|
|
|
|
|
|
|
|
|
|
|
|
Les commandes AVANT leur sortie : c'est ce qui rend l'étape rejouable
|
|
|
|
|
|
à la main, et c'est ainsi que les pannes de ce module ont été
|
|
|
|
|
|
diagnostiquées."""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
chemin = os.path.join(session, f"{nom}.log")
|
|
|
|
|
|
hote = (spec.get("host") or {}).get("target", "?")
|
|
|
|
|
|
vm = next((v for v in spec.get("vms") or [] if v["name"] == nom), {})
|
|
|
|
|
|
entete = [
|
|
|
|
|
|
"=" * 64,
|
|
|
|
|
|
" ERPLibre — création d'une VM sur Proxmox VE",
|
|
|
|
|
|
f" Date : {time.strftime('%Y-%m-%d %H:%M:%S')}",
|
|
|
|
|
|
f" VM : {nom} VMID {vm.get('vmid', '?')}",
|
|
|
|
|
|
f" Hôte : {hote}",
|
|
|
|
|
|
f" Stockage : {spec.get('storage')} "
|
|
|
|
|
|
f"{t('bridge')} : {spec.get('bridge')}",
|
|
|
|
|
|
f" Adresse : {(vm.get('ipconfig') or '').replace('ip=', '')}",
|
|
|
|
|
|
f" Ressources: {vm.get('vcpus', '?')} vCPU "
|
|
|
|
|
|
f"{vm.get('ram', '?')} Mo {vm.get('disk', '?')}",
|
|
|
|
|
|
"=" * 64,
|
|
|
|
|
|
"",
|
|
|
|
|
|
"---- commandes ----",
|
|
|
|
|
|
]
|
|
|
|
|
|
entete += [f" {c}" for c in cmds]
|
|
|
|
|
|
propre = pve.collapse_progress(pve.strip_ssh_noise(sortie or ""))
|
|
|
|
|
|
entete += ["", "---- sortie ----", propre.rstrip(), ""]
|
|
|
|
|
|
entete += [
|
|
|
|
|
|
(
|
|
|
|
|
|
f"---- fin : code {code}, {duree:.0f} s ----"
|
|
|
|
|
|
if isinstance(duree, (int, float))
|
|
|
|
|
|
else f"---- fin : code {code} ----"
|
|
|
|
|
|
)
|
|
|
|
|
|
]
|
|
|
|
|
|
with open(chemin, "w", encoding="utf-8") as fh:
|
|
|
|
|
|
fh.write("\n".join(entete) + "\n")
|
|
|
|
|
|
return chemin
|
|
|
|
|
|
|
[FIX] suivi : un relevé Proxmox jeté, et deux lignes qui montraient une autre machine
Sur trois VM d'un même Proxmox, une seule avait ses colonnes vides — et les
deux autres montraient les chiffres d'une AUTRE machine. Deux fautes, dont
une était le miroir d'un correctif précédent.
Le code de sortie de la suite distante est celui de son DERNIER maillon, la
sonde Odoo. Tant qu'Odoo n'écoute pas — c'est-à-dire pendant TOUTE
l'installation, précisément quand on regarde — la boucle finit en échec et le
relevé, parfait, était jeté. On avait corrigé l'erreur inverse, un code 0 pris
pour une réponse ; exiger 0 était la même faute retournée. Seule une liste de
ressources analysable prouve une réponse.
Pendant ce temps, « virsh domstats » indexe par NOM, et un nom se partage :
les deux VM qui avaient un homonyme LOCAL affichaient ses chiffres. Mesuré —
1,5 Gio de RAM sur 12 et 58 Gio de disque sur 65, quand la vraie tournait avec
3 Gio et 25. Les relevés locaux d'une VM qui vit ailleurs sont donc retirés
AVANT d'ajouter ceux de l'hôte : un hôte muet laisse la colonne VIDE, ce qui
est vrai. Une colonne vide se remarque ; une colonne juste et fausse, non.
L'alias enfin. Prendre le nom court quand il se trouvait libre donnait un parc
incohérent : sur ce même déploiement, deux VM ont reçu « hôte+vm » — leurs
noms étaient pris par des domaines locaux — et la troisième son nom court. Une
convention qui dépend de ce qui traîne dans le fichier n'est pas une
convention. Le nom chaîné est systématique.
--- EN ---
Of three VMs on one Proxmox, only one had empty columns — and the other two
showed ANOTHER machine's figures. Two defects, one the mirror of an earlier
fix.
A remote pipeline's exit code is its LAST link's, the Odoo probe. While Odoo
is not listening — that is, during the WHOLE install, exactly when you are
watching — the loop ends in failure and the reading, perfectly good, was
thrown away. We had fixed the opposite error, a 0 taken for an answer;
demanding 0 was the same mistake reversed. Only a parsable resource list
proves an answer.
Meanwhile "virsh domstats" indexes by NAME, and a name is shared: the two VMs
with a LOCAL namesake displayed its figures. Measured — 1.5 GiB of RAM out of
12 and 58 GiB of disk out of 65, while the real one ran on 3 GiB and 25. Local
readings for a VM that lives elsewhere are therefore dropped BEFORE the host's
are added: a silent host leaves the column EMPTY, which is true. An empty
column gets noticed; a plausible wrong one does not.
The alias, finally. Taking the short name while it happened to be free gave an
inconsistent fleet: in that same deployment two VMs got "host+vm" — their
names were held by local domains — and the third its short name. A convention
that depends on what happens to sit in the file is not a convention. The
chained name is now systematic.
Assisted-by: Claude Opus 5
2026-08-25 00:30:54 -04:00
|
|
|
|
def _pve_alias_names(self, nom, chaine, locaux=(), rebond=""):
|
|
|
|
|
|
"""UN seul nom pour l'entrée ~/.ssh/config : « hôte+vm ».
|
|
|
|
|
|
|
|
|
|
|
|
Deux noms sur la même ligne « Host » — le chaîné et le court —
|
|
|
|
|
|
étaient un doublon : ssh n'a besoin que d'un nom, et le second
|
|
|
|
|
|
n'ajoutait qu'une façon de plus d'écrire la même adresse. Rapporté.
|
|
|
|
|
|
|
|
|
|
|
|
Reste à choisir lequel, et c'est le chaîné. Prendre le nom court
|
|
|
|
|
|
quand il se trouvait libre donnait un parc INCOHÉRENT : sur un même
|
|
|
|
|
|
déploiement, deux VM recevaient « hôte+vm » — leurs noms étaient pris
|
|
|
|
|
|
par des domaines locaux — et la troisième son nom court. Rapporté
|
|
|
|
|
|
aussi. Une convention qui dépend de ce qui traîne dans le fichier
|
|
|
|
|
|
n'est pas une convention.
|
|
|
|
|
|
|
|
|
|
|
|
Le chaîné est donc systématique. Il dit où la machine vit, il ne peut
|
|
|
|
|
|
rien voler à un domaine local, et deux VM du même nom sur deux hôtes
|
|
|
|
|
|
Proxmox différents se distinguent d'elles-mêmes.
|
|
|
|
|
|
|
|
|
|
|
|
Rend (noms, volé) — la seconde valeur reste pour l'appelant, qui
|
|
|
|
|
|
signale au passage un nom qu'une VM locale porte aussi."""
|
|
|
|
|
|
return [chaine], (t("a local VM") if nom in locaux else "")
|
[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
|
|
|
|
|
[FIX] proxmox : l'ancienne entrée ssh s'en va avec la convention
Le nom chaîné devient systématique, mais les entrées écrites AVANT portent le
nom court — et rien ne les retirerait : elles ne déclarent pas le nom qu'on
écrit maintenant. Deux blocs mèneraient à la même machine, exactement ce
qu'on venait d'enlever.
Le ProxyJump tranche : un bloc qui rebondit par CET hôte est le nôtre, on le
retire. Celui d'une VM locale homonyme n'en a pas, et on n'y touche jamais ;
celui d'un autre hôte Proxmox non plus.
Le drapeau Odoo gagne son test au passage. Il tombait pour la même raison que
les colonnes vides — la sonde est le dernier maillon de la suite distante, et
un parc où une seule VM n'a pas d'Odoo, un hyperviseur imbriqué par exemple,
finit en échec. Vérifié sur les trois VM : l'hôte rend bien « ODOO » pour les
deux qui écoutent, et le navigateur répondait 303 pendant que la colonne
disait « — ».
--- EN ---
The chained name becomes systematic, but entries written BEFORE carry the
short one — and nothing would retire them: they do not declare the name we
now write. Two blocks would lead to the same machine, exactly what we had
just removed.
The ProxyJump decides: a block hopping through THIS host is ours, so it goes.
A local namesake's has none, and is never touched; another Proxmox host's
neither.
The Odoo flag gains its test along the way. It failed for the same reason as
the empty columns — the probe is the remote pipeline's last link, and a fleet
where a single VM has no Odoo, a nested hypervisor for instance, ends in
failure. Verified on all three VMs: the host does return "ODOO" for the two
that listen, and the browser answered 303 while the column said "—".
Assisted-by: Claude Opus 5
2026-08-25 00:43:10 -04:00
|
|
|
|
def _pve_alias_perime(self, nom, rebond):
|
|
|
|
|
|
"""Le nom court à RETIRER, s'il désigne encore cette VM-ci.
|
|
|
|
|
|
|
|
|
|
|
|
La convention a changé — le nom court d'abord, puis « hôte+vm » — et
|
|
|
|
|
|
rien ne retirerait l'ancien bloc : il ne porte pas le nom qu'on
|
|
|
|
|
|
écrit. Deux entrées mèneraient alors à la même machine, ce qu'on
|
|
|
|
|
|
venait justement d'enlever.
|
|
|
|
|
|
|
|
|
|
|
|
Le ProxyJump tranche : un bloc qui rebondit par CET hôte est le nôtre.
|
|
|
|
|
|
Celui d'une VM locale homonyme n'en a pas, et on n'y touche donc
|
|
|
|
|
|
jamais."""
|
|
|
|
|
|
bloc = self._ssh_config_block(nom)
|
|
|
|
|
|
return [nom] if bloc and bloc.get("proxyjump") == rebond else []
|
|
|
|
|
|
|
[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
|
|
|
|
def _pve_set_timezone(self, cible, spec):
|
|
|
|
|
|
"""Pose le fuseau DANS la VM, par ssh.
|
|
|
|
|
|
|
|
|
|
|
|
La voie libvirt le donne à cloud-init, qui écrit /etc/timezone au
|
|
|
|
|
|
premier démarrage. « qm set » n'a pas d'équivalent : le cloud-init de
|
|
|
|
|
|
Proxmox ne règle que l'utilisateur, la clé et le réseau. Une VM créée
|
|
|
|
|
|
ici restait donc en UTC — et on ne s'en aperçoit qu'aux horodatages,
|
|
|
|
|
|
parfois des jours plus tard.
|
|
|
|
|
|
|
|
|
|
|
|
AVANT l'installation, pour que le journal porte déjà la bonne heure.
|
|
|
|
|
|
Un nom IANA, jamais un décalage : « UTC-5 » ne dit rien de l'heure
|
|
|
|
|
|
d'été, et timedatectl le refuse.
|
|
|
|
|
|
"""
|
|
|
|
|
|
fuseau = (spec.get("timezone") or "").strip()
|
|
|
|
|
|
if not fuseau:
|
|
|
|
|
|
return False
|
|
|
|
|
|
code, sortie = self._pve_ssh(
|
|
|
|
|
|
cible, f"sudo timedatectl set-timezone {shlex.quote(fuseau)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
if code:
|
|
|
|
|
|
# Nommé et non tu : la VM reste en UTC, et c'est une surprise
|
|
|
|
|
|
# qu'on veut avoir maintenant plutôt qu'au premier journal.
|
|
|
|
|
|
print(f" ⚠ {t('timezone not set')} : {fuseau} ({code})")
|
|
|
|
|
|
return False
|
|
|
|
|
|
print(f" ✓ {t('Timezone')} : {fuseau}")
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
def _pve_cache_ca(self, host):
|
|
|
|
|
|
"""L'autorité du cache à poser dans les VM de cet hôte, ou ''.
|
|
|
|
|
|
|
|
|
|
|
|
Le cache détourne tout ce qui sort de SON pont. Un hôte Proxmox qui
|
|
|
|
|
|
est lui-même une VM d'ici y est branché, et les machines qu'il porte
|
|
|
|
|
|
sortent derrière son adresse : elles sont donc interceptées, sans que
|
|
|
|
|
|
rien à l'intérieur ne l'annonce. Un hôte Proxmox qui ne vit pas ici ne
|
|
|
|
|
|
traverse pas ce pont, et son invité n'a que faire de cette autorité.
|
|
|
|
|
|
|
|
|
|
|
|
Le déséquilibre décide du doute : une autorité approuvée en trop ne
|
|
|
|
|
|
signe jamais rien, tandis qu'un détournement sans autorité fait
|
|
|
|
|
|
échouer chaque téléchargement HTTPS sur « self-signed certificate in
|
|
|
|
|
|
certificate chain ». En cas d'hésitation, on la pose.
|
|
|
|
|
|
"""
|
|
|
|
|
|
nom = (host.get("target") or "").split("@")[-1]
|
|
|
|
|
|
if not nom or nom not in set(self._qemu_list_domains()):
|
|
|
|
|
|
return ""
|
|
|
|
|
|
return self._qemu_cache_ca_path()
|
|
|
|
|
|
|
[FIX] cache et journal : hôte imbriqué soustrait, décision écrite
Un invité sans magasin de confiance ne peut RIEN recevoir, et sur un hôte
Proxmox c'est l'HÔTE qu'il faut excepter : imbriqué, l'invité sort masqué
derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis
l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le
gestionnaire de paquets se rabattait sur 564 dérivations à construire.
Le journal par VM ne portait AUCUN message du menu : la cause vivait sur
une console qui défile, et le fichier qu'on rouvre après l'échec n'avait
que le symptôme. Et « db_drop_all » annonçait détruites des bases qui ne
l'étaient pas, son code de retour jeté.
--- EN ---
A guest with no trust store can receive NOTHING, and on a Proxmox host it
is the HOST that must be exempted: nested, the guest leaves masqueraded
behind it and the bridge never sees its own address. Measured from the
guest: code 000 and SSL verification 19, then 200 and 0. Without it the
package manager fell back to building 564 derivations.
The per-VM log carried NO message from the menu: the cause lived on a
console that scrolls away, and the file reopened after failure held only
the symptom. And "db_drop_all" announced databases as dropped that were
not, its exit status discarded.
Assisted-by: Claude Opus 5
2026-09-16 00:43:40 -04:00
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _pve_note(vm, ligne):
|
|
|
|
|
|
"""Dit la ligne à l'écran ET la garde pour le journal de CETTE VM.
|
|
|
|
|
|
|
|
|
|
|
|
La console défile et se perd ; le journal est ce qu'on rouvre quand
|
|
|
|
|
|
l'installation a échoué, parfois le lendemain. Une décision prise ici
|
|
|
|
|
|
— l'autorité du cache posée ou non, l'exception — n'explique la panne
|
|
|
|
|
|
que si elle atteint le second. Sans cela le journal ne porte que le
|
|
|
|
|
|
symptôme : des centaines de lignes de construction et un certificat
|
|
|
|
|
|
refusé, sans un mot sur ce qui l'a voulu.
|
|
|
|
|
|
"""
|
|
|
|
|
|
print(ligne)
|
|
|
|
|
|
vm.setdefault("notes", []).append(ligne.strip())
|
|
|
|
|
|
|
[FIX] cache et guide : suivre la traduction du binaire et le hors ligne
Le rebase apporte trois choses auxquelles ce travail devait s'accorder.
Les messages du binaire se traduisent désormais : ceux du refus de place
et de l'aide de --oublie passent par T(), avec leur entrée au catalogue —
deux garde-fous refusent sinon un message sans traduction ou l'inverse.
Soustraire l'hôte au cache est REFUSÉ hors ligne : le magasin est alors la
seule source, et l'excepter ne le ferait pas télécharger en direct, cela
le priverait de tout, ses propres paquets compris.
Et le guide de connexion annonce les outils sur la voie Proxmox aussi ; il
n'y était plus écrit du tout, l'argument manquant étant avalé.
--- EN ---
The rebase brings three things this work had to align with. The binary's
messages are now translatable: the out-of-space refusal and the --oublie
help go through T(), with their catalogue entry — two guard rails
otherwise refuse a message without a translation, or the reverse.
Exempting the host from the cache is REFUSED offline: the store is then
the only source, and exempting it would not make it download directly, it
would leave it with nothing, its own packages included.
And the connection guide announces the tools on the Proxmox path too; it
was no longer written there at all, the missing argument being swallowed.
Assisted-by: Claude Opus 5
2026-09-16 05:58:59 -04:00
|
|
|
|
def _pve_cache_bypass_hote(self, host, vm, hors_ligne=False):
|
[FIX] cache et journal : hôte imbriqué soustrait, décision écrite
Un invité sans magasin de confiance ne peut RIEN recevoir, et sur un hôte
Proxmox c'est l'HÔTE qu'il faut excepter : imbriqué, l'invité sort masqué
derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis
l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le
gestionnaire de paquets se rabattait sur 564 dérivations à construire.
Le journal par VM ne portait AUCUN message du menu : la cause vivait sur
une console qui défile, et le fichier qu'on rouvre après l'échec n'avait
que le symptôme. Et « db_drop_all » annonçait détruites des bases qui ne
l'étaient pas, son code de retour jeté.
--- EN ---
A guest with no trust store can receive NOTHING, and on a Proxmox host it
is the HOST that must be exempted: nested, the guest leaves masqueraded
behind it and the bridge never sees its own address. Measured from the
guest: code 000 and SSL verification 19, then 200 and 0. Without it the
package manager fell back to building 564 derivations.
The per-VM log carried NO message from the menu: the cause lived on a
console that scrolls away, and the file reopened after failure held only
the symptom. And "db_drop_all" announced databases as dropped that were
not, its exit status discarded.
Assisted-by: Claude Opus 5
2026-09-16 00:43:40 -04:00
|
|
|
|
"""Soustrait au cache l'hôte Proxmox qui porte un invité sans magasin.
|
|
|
|
|
|
|
|
|
|
|
|
Rend True quand l'exception est en place, False quand il n'y a rien à
|
|
|
|
|
|
faire ou qu'elle a échoué.
|
|
|
|
|
|
|
|
|
|
|
|
POURQUOI LA MAC DE L'HÔTE, ET NON CELLE DE L'INVITÉ. Un invité
|
|
|
|
|
|
imbriqué sort MASQUÉ derrière son hôte : sur le pont d'ici, le cache
|
|
|
|
|
|
ne voit jamais que la MAC de l'hôte Proxmox, et c'est donc elle
|
|
|
|
|
|
qu'il faut excepter. Mesuré des deux côtés — sans l'exception, une
|
|
|
|
|
|
requête de l'invité vers cache.nixos.org rend code 000 et
|
|
|
|
|
|
vérification SSL 19 ; avec, code 200 et vérification 0.
|
|
|
|
|
|
|
|
|
|
|
|
CE QUE COÛTE L'ABSENCE DE REMÈDE. Une distribution dont le magasin de
|
|
|
|
|
|
confiance n'a pas de forme par fichier ne peut pas recevoir
|
|
|
|
|
|
l'autorité, et poser celle-ci par déclaration arriverait trop tard :
|
|
|
|
|
|
sur un système déclaratif, la première reconstruction EST le premier
|
|
|
|
|
|
téléchargement. Le gestionnaire de paquets ne lit alors plus son cache
|
|
|
|
|
|
binaire, se rabat sur la construction depuis les sources — des
|
|
|
|
|
|
centaines de dérivations — et ces sources échouent pour la même
|
|
|
|
|
|
raison. L'installation part pour une heure avant de rendre 1.
|
|
|
|
|
|
|
|
|
|
|
|
CE QU'ELLE COÛTE. L'exception vaut pour TOUT ce que l'hôte relaie, y
|
|
|
|
|
|
compris ses propres téléchargements : il cesse de profiter du cache.
|
|
|
|
|
|
C'est le prix, et il est dit plutôt que subi.
|
[FIX] cache et guide : suivre la traduction du binaire et le hors ligne
Le rebase apporte trois choses auxquelles ce travail devait s'accorder.
Les messages du binaire se traduisent désormais : ceux du refus de place
et de l'aide de --oublie passent par T(), avec leur entrée au catalogue —
deux garde-fous refusent sinon un message sans traduction ou l'inverse.
Soustraire l'hôte au cache est REFUSÉ hors ligne : le magasin est alors la
seule source, et l'excepter ne le ferait pas télécharger en direct, cela
le priverait de tout, ses propres paquets compris.
Et le guide de connexion annonce les outils sur la voie Proxmox aussi ; il
n'y était plus écrit du tout, l'argument manquant étant avalé.
--- EN ---
The rebase brings three things this work had to align with. The binary's
messages are now translatable: the out-of-space refusal and the --oublie
help go through T(), with their catalogue entry — two guard rails
otherwise refuse a message without a translation, or the reverse.
Exempting the host from the cache is REFUSED offline: the store is then
the only source, and exempting it would not make it download directly, it
would leave it with nothing, its own packages included.
And the connection guide announces the tools on the Proxmox path too; it
was no longer written there at all, the missing argument being swallowed.
Assisted-by: Claude Opus 5
2026-09-16 05:58:59 -04:00
|
|
|
|
|
|
|
|
|
|
JAMAIS HORS LIGNE. L'amont du cache est alors coupé et le magasin est
|
|
|
|
|
|
la SEULE source : excepter l'hôte ne le ferait pas télécharger en
|
|
|
|
|
|
direct, cela le priverait de tout — ses propres paquets compris. Les
|
|
|
|
|
|
deux issues échouent pour l'invité, mais celle-ci emporte l'hôte avec
|
|
|
|
|
|
lui. On garde donc le cache, et l'avertissement qui suit dit à
|
|
|
|
|
|
l'invité ce qui l'attend.
|
[FIX] cache et journal : hôte imbriqué soustrait, décision écrite
Un invité sans magasin de confiance ne peut RIEN recevoir, et sur un hôte
Proxmox c'est l'HÔTE qu'il faut excepter : imbriqué, l'invité sort masqué
derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis
l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le
gestionnaire de paquets se rabattait sur 564 dérivations à construire.
Le journal par VM ne portait AUCUN message du menu : la cause vivait sur
une console qui défile, et le fichier qu'on rouvre après l'échec n'avait
que le symptôme. Et « db_drop_all » annonçait détruites des bases qui ne
l'étaient pas, son code de retour jeté.
--- EN ---
A guest with no trust store can receive NOTHING, and on a Proxmox host it
is the HOST that must be exempted: nested, the guest leaves masqueraded
behind it and the bridge never sees its own address. Measured from the
guest: code 000 and SSL verification 19, then 200 and 0. Without it the
package manager fell back to building 564 derivations.
The per-VM log carried NO message from the menu: the cause lived on a
console that scrolls away, and the file reopened after failure held only
the symptom. And "db_drop_all" announced databases as dropped that were
not, its exit status discarded.
Assisted-by: Claude Opus 5
2026-09-16 00:43:40 -04:00
|
|
|
|
"""
|
[FIX] cache et guide : suivre la traduction du binaire et le hors ligne
Le rebase apporte trois choses auxquelles ce travail devait s'accorder.
Les messages du binaire se traduisent désormais : ceux du refus de place
et de l'aide de --oublie passent par T(), avec leur entrée au catalogue —
deux garde-fous refusent sinon un message sans traduction ou l'inverse.
Soustraire l'hôte au cache est REFUSÉ hors ligne : le magasin est alors la
seule source, et l'excepter ne le ferait pas télécharger en direct, cela
le priverait de tout, ses propres paquets compris.
Et le guide de connexion annonce les outils sur la voie Proxmox aussi ; il
n'y était plus écrit du tout, l'argument manquant étant avalé.
--- EN ---
The rebase brings three things this work had to align with. The binary's
messages are now translatable: the out-of-space refusal and the --oublie
help go through T(), with their catalogue entry — two guard rails
otherwise refuse a message without a translation, or the reverse.
Exempting the host from the cache is REFUSED offline: the store is then
the only source, and exempting it would not make it download directly, it
would leave it with nothing, its own packages included.
And the connection guide announces the tools on the Proxmox path too; it
was no longer written there at all, the missing argument being swallowed.
Assisted-by: Claude Opus 5
2026-09-16 05:58:59 -04:00
|
|
|
|
if hors_ligne:
|
|
|
|
|
|
return False
|
[FIX] cache et journal : hôte imbriqué soustrait, décision écrite
Un invité sans magasin de confiance ne peut RIEN recevoir, et sur un hôte
Proxmox c'est l'HÔTE qu'il faut excepter : imbriqué, l'invité sort masqué
derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis
l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le
gestionnaire de paquets se rabattait sur 564 dérivations à construire.
Le journal par VM ne portait AUCUN message du menu : la cause vivait sur
une console qui défile, et le fichier qu'on rouvre après l'échec n'avait
que le symptôme. Et « db_drop_all » annonçait détruites des bases qui ne
l'étaient pas, son code de retour jeté.
--- EN ---
A guest with no trust store can receive NOTHING, and on a Proxmox host it
is the HOST that must be exempted: nested, the guest leaves masqueraded
behind it and the bridge never sees its own address. Measured from the
guest: code 000 and SSL verification 19, then 200 and 0. Without it the
package manager fell back to building 564 derivations.
The per-VM log carried NO message from the menu: the cause lived on a
console that scrolls away, and the file reopened after failure held only
the symptom. And "db_drop_all" announced databases as dropped that were
not, its exit status discarded.
Assisted-by: Claude Opus 5
2026-09-16 00:43:40 -04:00
|
|
|
|
try:
|
|
|
|
|
|
mod = self._qemu_import_module()
|
|
|
|
|
|
except Exception: # pragma: no cover - dépend du module
|
|
|
|
|
|
mod = None
|
|
|
|
|
|
if not mod:
|
|
|
|
|
|
# Un module qui ne se charge pas ne doit pas emporter la suite de
|
|
|
|
|
|
# la création : sans lui on ne sait pas si l'invité a un magasin
|
|
|
|
|
|
# de confiance, et l'autorité reste la voie par défaut.
|
|
|
|
|
|
return False
|
|
|
|
|
|
distro = vm.get("distro") or ""
|
|
|
|
|
|
if not mod.cache_sans_autorite(distro):
|
|
|
|
|
|
return False
|
|
|
|
|
|
nom = (host.get("target") or "").split("@")[-1]
|
|
|
|
|
|
if not nom or nom not in set(self._qemu_list_domains()):
|
|
|
|
|
|
# Un hôte Proxmox qui ne vit pas ici ne traverse pas ce pont.
|
|
|
|
|
|
return False
|
|
|
|
|
|
mac = self._qemu_domain_mac(nom)
|
|
|
|
|
|
if not mac:
|
|
|
|
|
|
self._pve_note(
|
|
|
|
|
|
vm, f" ⚠ {t('download cache: host MAC not found')} : {nom}"
|
|
|
|
|
|
)
|
|
|
|
|
|
return False
|
|
|
|
|
|
geste = (
|
|
|
|
|
|
f"{shlex.quote(mod.CACHE_BIN)} --bypass-add {shlex.quote(mac)}"
|
|
|
|
|
|
f" --bypass-name {shlex.quote(nom)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
# Le binaire écrit le fichier d'exceptions et rend sur sa sortie le
|
|
|
|
|
|
# geste à chaud ; sans nft, seul le redémarrage du service repose la
|
|
|
|
|
|
# chaîne entière.
|
|
|
|
|
|
if shutil.which("nft"):
|
|
|
|
|
|
cmd = ["sudo", "sh", "-c", f"{geste} | nft -f -"]
|
|
|
|
|
|
else:
|
|
|
|
|
|
cmd = ["sudo", "sh", "-c", geste]
|
|
|
|
|
|
try:
|
|
|
|
|
|
fini = subprocess.run(cmd, capture_output=True, text=True)
|
|
|
|
|
|
except (OSError, subprocess.SubprocessError) as souci:
|
|
|
|
|
|
print(f" ⚠ {t('download cache bypass not set')} ({souci})")
|
|
|
|
|
|
return False
|
|
|
|
|
|
if fini.returncode:
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" ⚠ {t('download cache bypass not set')}"
|
|
|
|
|
|
f" ({fini.returncode})"
|
|
|
|
|
|
)
|
|
|
|
|
|
return False
|
|
|
|
|
|
self._pve_note(
|
|
|
|
|
|
vm, f" ✓ {t('host taken out of the download cache')} : {nom}"
|
|
|
|
|
|
)
|
|
|
|
|
|
self._pve_note(
|
|
|
|
|
|
vm, f" {t('its own downloads stop being cached too.')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def _qemu_domain_mac(self, nom):
|
|
|
|
|
|
"""Première MAC du domaine libvirt `nom`, ou ''."""
|
|
|
|
|
|
try:
|
|
|
|
|
|
res = subprocess.run(
|
|
|
|
|
|
virsh_argv("domiflist", nom),
|
|
|
|
|
|
capture_output=True,
|
|
|
|
|
|
text=True,
|
|
|
|
|
|
timeout=15,
|
|
|
|
|
|
)
|
|
|
|
|
|
except (OSError, subprocess.SubprocessError):
|
|
|
|
|
|
return ""
|
|
|
|
|
|
trouve = re.findall(r"[0-9a-f]{2}(?::[0-9a-f]{2}){5}", res.stdout)
|
|
|
|
|
|
return trouve[0].lower() if trouve else ""
|
|
|
|
|
|
|
2026-09-16 03:00:17 -04:00
|
|
|
|
def _pve_set_cache_ca(self, cible, vm, ca, hors_ligne=False):
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
"""Pose l'autorité du cache DANS la VM, par ssh.
|
|
|
|
|
|
|
2026-09-16 03:00:17 -04:00
|
|
|
|
`hors_ligne` : l'amont du cache est coupé pour ce déploiement ; la VM
|
|
|
|
|
|
reçoit en plus ce que la voie libvirt pose sous « --offline ».
|
|
|
|
|
|
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
Même source que la voie libvirt — `cache_files` et `cache_commands` de
|
|
|
|
|
|
deploy_qemu — livrée autrement : « qm set » ne sait écrire aucun
|
|
|
|
|
|
fichier, comme pour le guide et pour le fuseau.
|
|
|
|
|
|
|
|
|
|
|
|
AVANT l'installation : c'est elle qui télécharge. Un magasin de
|
|
|
|
|
|
confiance relu ensuite ne rattrape rien de ce qui a déjà échoué.
|
|
|
|
|
|
"""
|
|
|
|
|
|
import types
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
mod = self._qemu_import_module()
|
|
|
|
|
|
except Exception: # pragma: no cover - dépend du module
|
|
|
|
|
|
return False
|
|
|
|
|
|
args = types.SimpleNamespace(
|
2026-09-16 03:00:17 -04:00
|
|
|
|
distro=vm.get("distro") or "",
|
|
|
|
|
|
cache_ca=ca,
|
|
|
|
|
|
cache_bypass=False,
|
|
|
|
|
|
offline=hors_ligne,
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
)
|
|
|
|
|
|
fichiers = mod.cache_files(args)
|
|
|
|
|
|
if not fichiers:
|
[FIX] cache qemu : soustraire l'invité qui n'a pas de magasin
Le détournement est TRANSPARENT et vaut pour tout le pont : ne pas donner
l'autorité à une VM ne la dispense pas d'être interceptée, elle échoue sur
« self-signed certificate in certificate chain ». NixOS n'a pas d'ancre de
confiance par fichier, et la poser par déclaration arriverait trop tard —
la première reconstruction EST le premier téléchargement. La VM est donc
exceptée par son adresse MAC, avant sa création, et cela se dit.
L'image Proxmox n'est mise en place qu'une fois complète : « wget -O »
écrivait dans la cible, et une coupure y figeait un fichier tronqué que
le test de présence acceptait à chaque déploiement suivant.
--- EN ---
Interception is TRANSPARENT and covers the whole bridge: withholding the
authority from a VM does not spare it, it fails on "self-signed
certificate in certificate chain". NixOS has no per-file trust anchor, and
declaring one would come too late — the first rebuild IS the first
download. The VM is therefore exempted by MAC, before creation, and it is
said.
The Proxmox image is put in place only once complete: "wget -O" wrote into
the target, and an interruption froze a truncated file there that the
presence test accepted on every later deployment.
Assisted-by: Claude Opus 5
2026-09-16 00:42:48 -04:00
|
|
|
|
# Une distribution dont le magasin de confiance n'a pas de forme
|
|
|
|
|
|
# par fichier — un système déclaratif n'a pas d'ancre où écrire —
|
|
|
|
|
|
# ou une autorité illisible. Poser le fichier au mauvais endroit
|
|
|
|
|
|
# ne marcherait pas.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Mais le silence était faux : on n'arrive ici que lorsque l'hôte
|
|
|
|
|
|
# Proxmox est lui-même une VM de CE pont, donc ses invités sont
|
|
|
|
|
|
# détournés. Celui-ci n'aura rien pour reconnaître le cache, et
|
|
|
|
|
|
# chaque téléchargement HTTPS échouera sur « self-signed
|
|
|
|
|
|
# certificate in certificate chain » — plus tard, dans la VM,
|
|
|
|
|
|
# loin d'ici. C'est pourquoi il est nommé.
|
|
|
|
|
|
cle = (
|
|
|
|
|
|
"no trust store for this distribution, its downloads "
|
|
|
|
|
|
"will fail"
|
|
|
|
|
|
)
|
[FIX] cache et journal : hôte imbriqué soustrait, décision écrite
Un invité sans magasin de confiance ne peut RIEN recevoir, et sur un hôte
Proxmox c'est l'HÔTE qu'il faut excepter : imbriqué, l'invité sort masqué
derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis
l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le
gestionnaire de paquets se rabattait sur 564 dérivations à construire.
Le journal par VM ne portait AUCUN message du menu : la cause vivait sur
une console qui défile, et le fichier qu'on rouvre après l'échec n'avait
que le symptôme. Et « db_drop_all » annonçait détruites des bases qui ne
l'étaient pas, son code de retour jeté.
--- EN ---
A guest with no trust store can receive NOTHING, and on a Proxmox host it
is the HOST that must be exempted: nested, the guest leaves masqueraded
behind it and the bridge never sees its own address. Measured from the
guest: code 000 and SSL verification 19, then 200 and 0. Without it the
package manager fell back to building 564 derivations.
The per-VM log carried NO message from the menu: the cause lived on a
console that scrolls away, and the file reopened after failure held only
the symptom. And "db_drop_all" announced databases as dropped that were
not, its exit status discarded.
Assisted-by: Claude Opus 5
2026-09-16 00:43:40 -04:00
|
|
|
|
self._pve_note(vm, f" ⚠ {t(cle)} : {vm.get('distro') or '?'}")
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
return False
|
|
|
|
|
|
morceaux = []
|
|
|
|
|
|
for chemin, mode, contenu, _proprio in fichiers:
|
|
|
|
|
|
q = shlex.quote(chemin)
|
|
|
|
|
|
morceaux.append(
|
|
|
|
|
|
f"printf '%s' {shlex.quote(contenu)} | sudo tee {q} "
|
|
|
|
|
|
f">/dev/null && sudo chmod {mode} {q}"
|
|
|
|
|
|
)
|
|
|
|
|
|
morceaux += [
|
|
|
|
|
|
f"sudo sh -c {shlex.quote(c)}" for c in mod.cache_commands(args)
|
|
|
|
|
|
]
|
|
|
|
|
|
code, _o = self._pve_ssh(cible, " && ".join(morceaux), timeout=120)
|
|
|
|
|
|
if code:
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" ⚠ {t('download cache authority not installed')} ({code})"
|
|
|
|
|
|
)
|
|
|
|
|
|
return False
|
[FIX] cache et journal : hôte imbriqué soustrait, décision écrite
Un invité sans magasin de confiance ne peut RIEN recevoir, et sur un hôte
Proxmox c'est l'HÔTE qu'il faut excepter : imbriqué, l'invité sort masqué
derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis
l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le
gestionnaire de paquets se rabattait sur 564 dérivations à construire.
Le journal par VM ne portait AUCUN message du menu : la cause vivait sur
une console qui défile, et le fichier qu'on rouvre après l'échec n'avait
que le symptôme. Et « db_drop_all » annonçait détruites des bases qui ne
l'étaient pas, son code de retour jeté.
--- EN ---
A guest with no trust store can receive NOTHING, and on a Proxmox host it
is the HOST that must be exempted: nested, the guest leaves masqueraded
behind it and the bridge never sees its own address. Measured from the
guest: code 000 and SSL verification 19, then 200 and 0. Without it the
package manager fell back to building 564 derivations.
The per-VM log carried NO message from the menu: the cause lived on a
console that scrolls away, and the file reopened after failure held only
the symptom. And "db_drop_all" announced databases as dropped that were
not, its exit status discarded.
Assisted-by: Claude Opus 5
2026-09-16 00:43:40 -04:00
|
|
|
|
self._pve_note(vm, f" ✓ {t('download cache authority installed')}")
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_attendre_ssh(self, cible, delai=300, pas=10):
|
|
|
|
|
|
"""Attend que la VM réponde en ssh. Rend False si elle ne répond pas.
|
|
|
|
|
|
|
|
|
|
|
|
Une VM tout juste créée a une adresse bien avant d'avoir un sshd :
|
|
|
|
|
|
cloud-init pose les comptes et les clés, et cela prend des minutes
|
|
|
|
|
|
sur une machine émulée. Les étapes qui suivent passent toutes par
|
|
|
|
|
|
ssh, et les lancer trop tôt les fait échouer ENSEMBLE, chacune avec
|
|
|
|
|
|
son propre message — la panne ressemble alors à quatre pannes.
|
|
|
|
|
|
|
|
|
|
|
|
Bornée par le TEMPS : un essai coûte le délai de connexion de ssh,
|
|
|
|
|
|
que rien ici ne borne à l'avance.
|
|
|
|
|
|
"""
|
|
|
|
|
|
fin = time.time() + delai
|
|
|
|
|
|
premier = True
|
|
|
|
|
|
while time.time() < fin:
|
|
|
|
|
|
code, _o = self._pve_ssh(cible, "true", timeout=20)
|
|
|
|
|
|
if code == 0:
|
|
|
|
|
|
return True
|
|
|
|
|
|
if premier:
|
|
|
|
|
|
print(f" … {t('waiting for the VM to answer ssh')}")
|
|
|
|
|
|
premier = False
|
|
|
|
|
|
time.sleep(pas)
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_set_apt_mirror(self, cible, vm, mod=None):
|
|
|
|
|
|
"""Fixe le miroir apt de la VM sur celui que le cache a rempli.
|
|
|
|
|
|
|
|
|
|
|
|
Le magasin range ses index sous l'HÔTE demandé : une VM qui réclame
|
|
|
|
|
|
« archive.ubuntu.com » ne retrouve rien de ce qu'une autre a gardé
|
|
|
|
|
|
depuis un miroir, et hors ligne chacun de ces index manque — la suite
|
|
|
|
|
|
échoue alors sur des dépendances introuvables, ce qui accuse le dépôt
|
|
|
|
|
|
et non le miroir. La voie libvirt écrit le miroir dans le
|
|
|
|
|
|
cloud-config ; « qm set » ne sait écrire aucun fichier, d'où ce
|
|
|
|
|
|
passage par ssh.
|
|
|
|
|
|
|
|
|
|
|
|
Ubuntu seulement : Debian, Fedora et Arch ont leurs propres dépôts, et
|
|
|
|
|
|
y réécrire une URI ubuntu ne viserait rien. Les deux formats sont
|
|
|
|
|
|
couverts — le « .sources » deb822 des images récentes et le
|
|
|
|
|
|
« sources.list » des anciennes — et « security » suit le même miroir,
|
|
|
|
|
|
que les miroirs répliquent sous le même chemin.
|
|
|
|
|
|
"""
|
|
|
|
|
|
if (vm.get("distro") or "") != "ubuntu":
|
|
|
|
|
|
return False
|
|
|
|
|
|
ports = vm.get("arch") in (getattr(mod, "PORTS_ARCHES", ()) or ())
|
|
|
|
|
|
miroirs = (
|
|
|
|
|
|
getattr(mod, "APT_MIRRORS_PORTS", ())
|
|
|
|
|
|
if ports
|
|
|
|
|
|
else getattr(mod, "APT_MIRRORS_MAIN", ())
|
|
|
|
|
|
) or ()
|
|
|
|
|
|
if not miroirs:
|
|
|
|
|
|
return False
|
|
|
|
|
|
miroir = miroirs[0]
|
|
|
|
|
|
# Les arches « ports » ne sont pas sur archive.ubuntu.com, et amd64
|
|
|
|
|
|
# n'est pas sur ports.ubuntu.com : le motif suit l'architecture.
|
|
|
|
|
|
motif = (
|
|
|
|
|
|
r"https?://ports\.ubuntu\.com/ubuntu-ports"
|
|
|
|
|
|
if ports
|
|
|
|
|
|
else r"https?://(archive|security)\.ubuntu\.com/ubuntu"
|
|
|
|
|
|
)
|
|
|
|
|
|
# « # » comme séparateur de sed : une URL en est dépourvue, alors
|
|
|
|
|
|
# qu'elle porte des « / » en quantité.
|
|
|
|
|
|
geste = (
|
|
|
|
|
|
f"sudo sed -i -E 's#{motif}#{miroir}#g'"
|
|
|
|
|
|
" /etc/apt/sources.list /etc/apt/sources.list.d/*.sources"
|
|
|
|
|
|
" /etc/apt/sources.list.d/*.list 2>/dev/null; true"
|
|
|
|
|
|
)
|
|
|
|
|
|
code, _o = self._pve_ssh(cible, geste, timeout=60)
|
|
|
|
|
|
if code:
|
|
|
|
|
|
print(f" ⚠ {t('apt mirror not pinned')} ({code})")
|
|
|
|
|
|
return False
|
|
|
|
|
|
print(f" ✓ {t('apt mirror pinned')} : {miroir}")
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
[ADD] proxmox deploy : accélération 3D, bibliothèques sondées et posées
A Proxmox VM always used the serial console as its display, so nothing inside
it could render. The form offers 3D: the VM gets « --vga virtio-gl » and the
account joins the render and video groups in the guest, which « qm set »
cannot write; without them GL falls back to software while VIRGL reports
success. The box is offered only where the host has a render node, VIRGL, GL
and EGL, Proxmox refusing to start the VM otherwise once its disk is written.
What is missing is named with its package, and a button installs it, then
reads the host again. Covered by test/test_proxmox_form.py.
--- FR ---
Une VM Proxmox prenait toujours la console série pour affichage : rien ne
pouvait y rendre. Le formulaire offre la 3D : la VM reçoit « --vga virtio-gl »
et le compte entre dans les groupes render et video dans l'invité, ce que
« qm set » ne sait pas écrire ; sans eux, GL retombe en logiciel alors que
VIRGL annonce un succès. La case n'est offerte que si l'hôte a un nœud de
rendu, VIRGL, GL et EGL, Proxmox refusant sinon de démarrer la VM une fois son
disque écrit. Ce qui manque est nommé avec son paquet, et un bouton le pose
puis relit l'hôte. Couvert par test/test_proxmox_form.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:20 -04:00
|
|
|
|
def _pve_set_gpu_groups(self, cible, utilisateur, mod=None):
|
|
|
|
|
|
"""Met le compte de la VM dans les groupes du GPU, par ssh.
|
|
|
|
|
|
|
|
|
|
|
|
Le nœud de rendu appartient à « root:render » en 0660 : un compte qui
|
|
|
|
|
|
n'y est pas retombe en rendu logiciel alors même que la négociation
|
|
|
|
|
|
VIRGL entre l'hôte et l'invité a réussi, et rien ne le signale — le
|
|
|
|
|
|
matériel virtuel est bien accéléré, seul l'accès manque.
|
|
|
|
|
|
|
|
|
|
|
|
La voie libvirt pose ces groupes par le cloud-config ; « qm set » ne
|
|
|
|
|
|
sait écrire aucun fichier, d'où ce passage par ssh. Les groupes sont
|
|
|
|
|
|
CRÉÉS au besoin : « render » manque des images les plus anciennes, et
|
|
|
|
|
|
« usermod -aG » sur un groupe inconnu échoue.
|
|
|
|
|
|
|
|
|
|
|
|
Les appartenances ne valent qu'à la PROCHAINE session : celle qui
|
|
|
|
|
|
tourne garde les siennes, ce que l'installation qui suit ne subit pas,
|
|
|
|
|
|
chacune de ses commandes ouvrant sa propre session.
|
|
|
|
|
|
"""
|
|
|
|
|
|
groupes = list(getattr(mod, "GPU_GROUPS", ()) or ("render", "video"))
|
|
|
|
|
|
gestes = [f"sudo groupadd -f {g}" for g in groupes]
|
|
|
|
|
|
gestes.append(
|
|
|
|
|
|
f"sudo usermod -aG {','.join(groupes)}"
|
|
|
|
|
|
f" {shlex.quote(utilisateur)}"
|
|
|
|
|
|
)
|
|
|
|
|
|
code, _o = self._pve_ssh(cible, " && ".join(gestes), timeout=60)
|
|
|
|
|
|
if code:
|
|
|
|
|
|
print(f" ⚠ {t('GPU groups not set')} ({code})")
|
|
|
|
|
|
return False
|
|
|
|
|
|
print(f" ✓ {t('GPU groups set')} : {', '.join(groupes)}")
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
[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
|
|
|
|
def _pve_write_guide(self, cible, vm, spec, mod):
|
|
|
|
|
|
"""Pose le guide de connexion et l'identité git DANS la VM.
|
|
|
|
|
|
|
|
|
|
|
|
La voie libvirt les livre par le « write_files » de cloud-init ;
|
|
|
|
|
|
« qm set » n'offre pas cela, donc une VM Proxmox n'avait AUCUN guide —
|
|
|
|
|
|
quelle que soit sa distribution. Rapporté sur Arch.
|
|
|
|
|
|
|
|
|
|
|
|
Même contenu, livrée par ssh une fois la VM debout : `guide_files` est
|
|
|
|
|
|
la source unique, comme sa docstring le promet. Un seul appel, tous les
|
|
|
|
|
|
fichiers.
|
|
|
|
|
|
"""
|
|
|
|
|
|
import types
|
|
|
|
|
|
|
|
|
|
|
|
from script.todo.todo_i18n import get_lang
|
|
|
|
|
|
|
|
|
|
|
|
install = spec.get("install") or {}
|
|
|
|
|
|
cmd_install = vm.get("install_cmd") or install.get("cmd") or ""
|
|
|
|
|
|
args = types.SimpleNamespace(
|
|
|
|
|
|
distro=vm.get("distro") or "",
|
|
|
|
|
|
version=vm.get("version") or "",
|
|
|
|
|
|
arch=vm.get("arch") or "amd64",
|
|
|
|
|
|
lang=get_lang(),
|
|
|
|
|
|
# La section ERPLibre n'apparaît que si ERPLibre y sera : un guide
|
|
|
|
|
|
# qui annonce un dépôt absent est un guide qui ment.
|
|
|
|
|
|
erplibre_dir=(
|
|
|
|
|
|
self._qemu_guide_dir(False)
|
|
|
|
|
|
if self._qemu_installs_erplibre(
|
|
|
|
|
|
install.get("branch"), cmd_install
|
|
|
|
|
|
)
|
|
|
|
|
|
else ""
|
|
|
|
|
|
),
|
|
|
|
|
|
erplibre_make=self._qemu_make_target(cmd_install),
|
|
|
|
|
|
desktop=bool(vm.get("desktop")),
|
[FIX] cache et guide : suivre la traduction du binaire et le hors ligne
Le rebase apporte trois choses auxquelles ce travail devait s'accorder.
Les messages du binaire se traduisent désormais : ceux du refus de place
et de l'aide de --oublie passent par T(), avec leur entrée au catalogue —
deux garde-fous refusent sinon un message sans traduction ou l'inverse.
Soustraire l'hôte au cache est REFUSÉ hors ligne : le magasin est alors la
seule source, et l'excepter ne le ferait pas télécharger en direct, cela
le priverait de tout, ses propres paquets compris.
Et le guide de connexion annonce les outils sur la voie Proxmox aussi ; il
n'y était plus écrit du tout, l'argument manquant étant avalé.
--- EN ---
The rebase brings three things this work had to align with. The binary's
messages are now translatable: the out-of-space refusal and the --oublie
help go through T(), with their catalogue entry — two guard rails
otherwise refuse a message without a translation, or the reverse.
Exempting the host from the cache is REFUSED offline: the store is then
the only source, and exempting it would not make it download directly, it
would leave it with nothing, its own packages included.
And the connection guide announces the tools on the Proxmox path too; it
was no longer written there at all, the missing argument being swallowed.
Assisted-by: Claude Opus 5
2026-09-16 05:58:59 -04:00
|
|
|
|
# Les outils que le guide annoncera, FILTRÉS par cette machine
|
|
|
|
|
|
# comme la voie libvirt le fait : en annoncer un que l'
|
|
|
|
|
|
# architecture ou l'absence de bureau écarte enverrait chercher
|
|
|
|
|
|
# une commande qui ne sera jamais posée.
|
|
|
|
|
|
vm_tools=",".join(
|
|
|
|
|
|
self._qemu_tools_for(
|
|
|
|
|
|
spec.get("vm_tools") or (),
|
|
|
|
|
|
vm.get("arch") or "amd64",
|
|
|
|
|
|
bool(vm.get("desktop")),
|
|
|
|
|
|
vm.get("distro") or "",
|
|
|
|
|
|
)
|
|
|
|
|
|
),
|
[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
|
|
|
|
no_git_identity=False,
|
|
|
|
|
|
user=spec.get("user") or "erplibre",
|
|
|
|
|
|
)
|
|
|
|
|
|
try:
|
|
|
|
|
|
fichiers = mod.guide_files(args)
|
|
|
|
|
|
except Exception as exc: # pragma: no cover - dépend du module
|
|
|
|
|
|
print(f" ⚠ {t('guide not written')} : {exc}")
|
|
|
|
|
|
return False
|
|
|
|
|
|
morceaux = []
|
|
|
|
|
|
for chemin, mode, contenu, proprio in fichiers:
|
|
|
|
|
|
q = shlex.quote(chemin)
|
|
|
|
|
|
morceaux.append(
|
|
|
|
|
|
f"printf '%s' {shlex.quote(contenu)} | sudo tee {q} "
|
|
|
|
|
|
f">/dev/null && sudo chmod {mode} {q}"
|
|
|
|
|
|
)
|
|
|
|
|
|
if proprio:
|
|
|
|
|
|
morceaux.append(f"sudo chown {shlex.quote(proprio)}: {q}")
|
|
|
|
|
|
code, _o = self._pve_ssh(cible, " && ".join(morceaux))
|
|
|
|
|
|
if code:
|
|
|
|
|
|
print(f" ⚠ {t('guide not written')} ({code})")
|
|
|
|
|
|
return False
|
|
|
|
|
|
print(f" ✓ {t('connection guide written')}")
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _pve_ssh(cible, remote, timeout=60):
|
|
|
|
|
|
"""(code, sortie) d'une commande exécutée DANS la VM, par son alias.
|
|
|
|
|
|
|
|
|
|
|
|
Par l'alias et non par l'adresse : lui seul porte le rebond vers le
|
|
|
|
|
|
réseau interne de l'hôte."""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
argv = [
|
|
|
|
|
|
"ssh",
|
|
|
|
|
|
"-o",
|
|
|
|
|
|
"BatchMode=yes",
|
|
|
|
|
|
"-o",
|
|
|
|
|
|
"StrictHostKeyChecking=no",
|
|
|
|
|
|
"-o",
|
|
|
|
|
|
"UserKnownHostsFile=/dev/null",
|
|
|
|
|
|
"-o",
|
|
|
|
|
|
"ConnectTimeout=10",
|
|
|
|
|
|
cible,
|
|
|
|
|
|
remote,
|
|
|
|
|
|
]
|
|
|
|
|
|
try:
|
|
|
|
|
|
res = subprocess.run(
|
|
|
|
|
|
argv, capture_output=True, text=True, timeout=timeout
|
|
|
|
|
|
)
|
|
|
|
|
|
except (OSError, subprocess.SubprocessError) as exc:
|
|
|
|
|
|
return 255, str(exc)
|
|
|
|
|
|
return res.returncode, pve.strip_ssh_noise(
|
|
|
|
|
|
(res.stdout or "") + (res.stderr or "")
|
|
|
|
|
|
)
|
|
|
|
|
|
|
[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
|
|
|
|
def _pve_print_summary(self, spec, joignables, session):
|
|
|
|
|
|
"""Sommaire final : ce qui existe, où, et comment y entrer.
|
|
|
|
|
|
|
|
|
|
|
|
Le pendant de celui de QEMU/KVM. Sans lui, l'écran se refermait sur la
|
|
|
|
|
|
vue de progression et il ne restait rien à l'écran — ni l'adresse, ni
|
|
|
|
|
|
la commande ssh, ni le chemin du journal."""
|
|
|
|
|
|
print(f"\n{'═' * 60}")
|
|
|
|
|
|
print(f" {t('TOTAL summary')}")
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" {t('VMs deployed:')} {len(joignables)}/{len(spec['vms'])}"
|
|
|
|
|
|
f" {t('storage')} {spec.get('storage')}"
|
|
|
|
|
|
f" {t('bridge')} {spec.get('bridge')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
for vm in joignables:
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" {vm['name']:<32} {t('VMID')} {vm.get('vmid', '?'):<6}"
|
|
|
|
|
|
f" {vm.get('adresse', '?')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
if vm.get("alias"):
|
|
|
|
|
|
print(f" ssh {vm['alias']}")
|
[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
|
|
|
|
# Une VM qui vient de recevoir Proxmox tourne encore le noyau de
|
|
|
|
|
|
# son image cloud : celui-ci n'a AUCUN module netfilter, donc ni
|
[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
|
|
|
|
# pont NAT ni VM à l'intérieur.
|
|
|
|
|
|
#
|
|
|
|
|
|
# Le suivi s'en charge : son enveloppe tourne sur NOTRE machine,
|
|
|
|
|
|
# donc elle survit au redémarrage de la VM, l'attend et vérifie le
|
|
|
|
|
|
# noyau avant de conclure. La note ne sert donc QUE sans suivi —
|
|
|
|
|
|
# la voie en série, elle, s'arrête à la fin du script. L'afficher
|
|
|
|
|
|
# dans les deux cas demanderait un redémarrage déjà fait.
|
|
|
|
|
|
if not spec.get("monitor", True) and self._pve_installs_proxmox(
|
|
|
|
|
|
vm, spec
|
|
|
|
|
|
):
|
[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
|
|
|
|
print(
|
|
|
|
|
|
f" ⚠ {t('reboot it to boot the Proxmox kernel:')}"
|
|
|
|
|
|
f" ssh {vm.get('alias') or vm['name']} sudo reboot"
|
|
|
|
|
|
)
|
[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
|
|
|
|
if spec.get("install"):
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" {t('Install:')} {spec['install'].get('label') or ''}"
|
|
|
|
|
|
f" ({spec['install'].get('branch')})"
|
|
|
|
|
|
)
|
|
|
|
|
|
print(f" {t('Log:')} {session}")
|
|
|
|
|
|
|
[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
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _pve_installs_proxmox(vm, spec) -> bool:
|
|
|
|
|
|
"""Cette VM reçoit-elle l'hyperviseur Proxmox VE ?
|
|
|
|
|
|
|
|
|
|
|
|
Jugé sur la commande EFFECTIVE de la VM — celle que son système lui
|
|
|
|
|
|
impose, sinon le choix commun — et non sur son nom ni sur sa
|
|
|
|
|
|
distribution : un parc mixte est le cas normal ici."""
|
|
|
|
|
|
cmd = vm.get("install_cmd") or (spec.get("install") or {}).get("cmd")
|
|
|
|
|
|
return "install_proxmox.sh" in (cmd or "")
|
|
|
|
|
|
|
[ADD] proxmox : récapituler dans le terminal avant de créer
L'écran montre le plan, mais la création part sur une machine qui n'est pas
la nôtre, et l'hôte va demander sudo juste après. La ligne dit donc où,
quoi et combien — hôte, stockage, pont, puis une ligne par VM avec son VMID
et son adresse — et attend un oui. Le déploiement QEMU/KVM le fait déjà ;
c'était la seule étape qui manquait ici.
--- EN ---
The screen shows the plan, but creation goes to a machine that is not ours,
and the host will ask for sudo right after. So the recap says where, what
and how many — host, storage, bridge, then one line per VM with its VMID
and address — and waits for a yes. The QEMU/KVM deployment already does
this; it was the only step missing here.
Assisted-by: Claude Opus 5
2026-08-23 18:05:55 -04:00
|
|
|
|
def _pve_confirm_spec(self, host, spec):
|
|
|
|
|
|
"""Récapitulatif puis confirmation, dans le TERMINAL.
|
|
|
|
|
|
|
|
|
|
|
|
L'écran a montré le plan, mais c'est ici que ça devient réel — et sur
|
|
|
|
|
|
une machine qui n'est pas la nôtre. La ligne dit donc où, quoi, et
|
|
|
|
|
|
combien, avant le mot de passe sudo que l'hôte va demander."""
|
|
|
|
|
|
print(f"\n {t('Proxmox host')} : {self._pve_label(host)}")
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" {t('storage')} {spec['storage']} "
|
|
|
|
|
|
f"{t('bridge')} {spec['bridge']} [{spec['res_label']}]"
|
|
|
|
|
|
)
|
|
|
|
|
|
for vm in spec["vms"]:
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" {vm['name']:32} {t('VMID')} {vm['vmid']} "
|
|
|
|
|
|
f"{vm['vcpus']} vCPU {vm['ram']} Mo {vm['disk']} "
|
|
|
|
|
|
f"{(vm.get('ipconfig') or '').replace('ip=', '')}"
|
|
|
|
|
|
)
|
|
|
|
|
|
if spec.get("install"):
|
|
|
|
|
|
print(
|
|
|
|
|
|
f" ERPLibre : {spec['install'].get('label') or ''}"
|
|
|
|
|
|
f" ({spec['install'].get('branch')})"
|
|
|
|
|
|
)
|
[ADD] déploiement : la VM clone le dépôt distant, pas ce checkout
« Le problème est revenu » — alors qu'il était corrigé la veille. La VM ne
reçoit pas le checkout d'ici : elle CLONE la branche depuis le dépôt distant.
Tout ce qui tourne dedans — install_proxmox.sh, les scripts d'installation, le
Makefile — vient donc de là.
Vécu deux fois de suite. Le correctif de /etc/hosts était commité ici, absent
du distant : chaque VM déployée ensuite recevait l'ancien script, et le même
défaut revenait à l'identique. Rien ne le disait, et il a fallu comparer les
deux versions du fichier à la main pour comprendre. Soixante-et-onze commits
séparaient les deux.
L'écart est donc dit AVANT de déployer, là où l'on peut encore renoncer : le
nombre, les trois premiers sujets, et « git push ». Sur les deux voies, car
les deux clonent.
Une branche que le distant ne connaît pas n'est pas un écart — c'est une
question qui ne se pose pas. La dire quand même vaudrait un avertissement à
chaque déploiement d'une branche neuve.
--- EN ---
"The problem came back" — though it had been fixed the day before. The VM does
not receive this checkout: it CLONES the branch from the remote. Everything
that runs inside it — install_proxmox.sh, the install scripts, the Makefile —
comes from there.
Twice in a row. The /etc/hosts fix was committed here and absent from the
remote: every VM deployed afterwards got the old script, and the same defect
returned unchanged. Nothing said so, and it took comparing both versions of
the file by hand to understand. Seventy-one commits separated them.
The gap is therefore stated BEFORE deploying, where you can still back out:
the count, the first three subjects, and "git push". On both paths, since both
clone.
A branch the remote does not know is not a gap — it is a question that does
not arise. Saying it anyway would mean a warning on every deployment of a new
branch.
Assisted-by: Claude Opus 5
(cherry picked from commit de27be5e736eb6e9bd01efd292e01c3b2231f91a)
2026-08-25 06:19:21 -04:00
|
|
|
|
# La VM CLONE la branche depuis le dépôt distant : tout ce qui
|
|
|
|
|
|
# tourne dedans — install_proxmox.sh compris — vient de là, pas
|
|
|
|
|
|
# d'ici. Un correctif non poussé est invisible pour elle.
|
|
|
|
|
|
for ligne in self._qemu_branch_gap_lines(
|
|
|
|
|
|
spec["install"].get("branch") or ""
|
|
|
|
|
|
):
|
|
|
|
|
|
print(f" {ligne}")
|
[ADD] proxmox : récapituler dans le terminal avant de créer
L'écran montre le plan, mais la création part sur une machine qui n'est pas
la nôtre, et l'hôte va demander sudo juste après. La ligne dit donc où,
quoi et combien — hôte, stockage, pont, puis une ligne par VM avec son VMID
et son adresse — et attend un oui. Le déploiement QEMU/KVM le fait déjà ;
c'était la seule étape qui manquait ici.
--- EN ---
The screen shows the plan, but creation goes to a machine that is not ours,
and the host will ask for sudo right after. So the recap says where, what
and how many — host, storage, bridge, then one line per VM with its VMID
and address — and waits for a yes. The QEMU/KVM deployment already does
this; it was the only step missing here.
Assisted-by: Claude Opus 5
2026-08-23 18:05:55 -04:00
|
|
|
|
return self._is_yes_default_yes(
|
|
|
|
|
|
input(f"\n{t('Deploy this VM now? (Y/n): ')}")
|
|
|
|
|
|
)
|
|
|
|
|
|
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
def _pve_after_create(
|
|
|
|
|
|
self, host, spec, reussies, cle_locale, coupee=False, debut=None
|
|
|
|
|
|
):
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
"""Ce qui suit la création : l'adresse, ~/.ssh/config, l'installation.
|
|
|
|
|
|
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
`coupee` et `debut` suivent jusqu'à l'installateur : le premier lui
|
|
|
|
|
|
fait confier la levée de la coupure au guet, le second date le
|
|
|
|
|
|
déploiement dans le manifeste, où le bilan hors ligne le lit.
|
|
|
|
|
|
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
L'alias et non l'IP dans les étapes suivantes : ssh y lit le rebond
|
|
|
|
|
|
par l'hôte Proxmox, et le suivi d'installation en a besoin pour
|
|
|
|
|
|
entrer dans une VM qui n'est pas sur notre réseau."""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
[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
|
|
|
|
# Les domaines LOCAUX : un nom partagé avec l'un d'eux fait dérailler
|
|
|
|
|
|
# l'alias ssh et le suivi d'installation.
|
|
|
|
|
|
locaux = set(self._qemu_list_domains())
|
[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
|
|
|
|
try:
|
|
|
|
|
|
mod_qemu = self._qemu_import_module()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
mod_qemu = None
|
[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
|
|
|
|
|
|
|
|
|
|
def alias_chaine(nom):
|
|
|
|
|
|
"""« hôte+vm », la convention déjà utilisée pour les VM
|
|
|
|
|
|
imbriquées : elle dit où la machine vit, et n'entre en conflit
|
|
|
|
|
|
avec rien."""
|
|
|
|
|
|
hote = (host.get("target") or "").split("@")[-1]
|
|
|
|
|
|
hote = re.sub(r"[^A-Za-z0-9._-]", "-", hote) or "pve"
|
|
|
|
|
|
return f"{hote}+{nom}"
|
|
|
|
|
|
|
|
|
|
|
|
# {nom de VM: alias à utiliser} — le suivi doit passer par l'alias
|
|
|
|
|
|
# qu'on a RÉELLEMENT écrit, pas par le nom.
|
|
|
|
|
|
alias = {}
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
joignables = []
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
ca_cache = self._pve_cache_ca(host)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
for vm in spec["vms"]:
|
|
|
|
|
|
if vm["name"] not in reussies:
|
|
|
|
|
|
continue
|
|
|
|
|
|
ip = pve.ip_from_ipconfig(vm.get("ipconfig") or "")
|
|
|
|
|
|
if not ip:
|
|
|
|
|
|
print(f"\n {t('Waiting for the VM address…')} {vm['name']}")
|
|
|
|
|
|
ip = self._pve_guest_ip(vm["vmid"])
|
|
|
|
|
|
if not ip:
|
[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
|
|
|
|
print(
|
|
|
|
|
|
f" ⚠ {vm['name']} : {t('No address yet. Try [6] later.')}"
|
|
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
continue
|
|
|
|
|
|
print(f" ✓ {vm['name']} : {ip}")
|
[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
|
|
|
|
# Un nom qui existe DÉJÀ comme domaine local est un piège : l'alias
|
|
|
|
|
|
# ~/.ssh/config serait volé à la VM locale, et le suivi
|
|
|
|
|
|
# d'installation — qui ré-résout par virsh — irait installer
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
# ERPLibre sur ELLE : une VM Proxmox homonyme d'un domaine
|
|
|
|
|
|
# local lui prend son alias, et l'installation part sur elle.
|
[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
|
|
|
|
noms_alias, vole = self._pve_alias_names(
|
|
|
|
|
|
vm["name"],
|
|
|
|
|
|
alias_chaine(vm["name"]),
|
|
|
|
|
|
locaux,
|
|
|
|
|
|
host["target"],
|
|
|
|
|
|
)
|
|
|
|
|
|
if vole:
|
[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
|
|
|
|
print(
|
[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
|
|
|
|
f" ⚠ {t('This name is already taken by')} {vole} :"
|
|
|
|
|
|
f" {t('the alias goes to')} {noms_alias[0]}"
|
[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
|
|
|
|
)
|
[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
|
|
|
|
# L'entrée ~/.ssh/config est le SEUL chemin vers cette VM : elle
|
|
|
|
|
|
# est derrière l'hôte Proxmox (pont interne), donc son adresse
|
|
|
|
|
|
# n'est pas routable d'ici et seul le rebond y mène. Décochée
|
|
|
|
|
|
# alors qu'une installation est demandée, l'installation suivie ne
|
|
|
|
|
|
# pouvait pas entrer — elle est donc écrite quand même, et on le
|
|
|
|
|
|
# dit. Sans installation ni suivi, le choix est respecté.
|
|
|
|
|
|
besoin = bool(spec.get("install")) or spec.get("monitor", True)
|
|
|
|
|
|
if not spec.get("add_ssh_config") and besoin:
|
|
|
|
|
|
print(f" → {t('~/.ssh/config written anyway (install)')}")
|
|
|
|
|
|
if spec.get("add_ssh_config") or besoin:
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
self._write_ssh_config_entry(
|
[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
|
|
|
|
noms_alias,
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
spec.get("user") or "erplibre",
|
|
|
|
|
|
ip,
|
|
|
|
|
|
identity_file=self._ssh_private_key(cle_locale),
|
|
|
|
|
|
proxy_jump=host["target"],
|
[FIX] proxmox : l'ancienne entrée ssh s'en va avec la convention
Le nom chaîné devient systématique, mais les entrées écrites AVANT portent le
nom court — et rien ne les retirerait : elles ne déclarent pas le nom qu'on
écrit maintenant. Deux blocs mèneraient à la même machine, exactement ce
qu'on venait d'enlever.
Le ProxyJump tranche : un bloc qui rebondit par CET hôte est le nôtre, on le
retire. Celui d'une VM locale homonyme n'en a pas, et on n'y touche jamais ;
celui d'un autre hôte Proxmox non plus.
Le drapeau Odoo gagne son test au passage. Il tombait pour la même raison que
les colonnes vides — la sonde est le dernier maillon de la suite distante, et
un parc où une seule VM n'a pas d'Odoo, un hyperviseur imbriqué par exemple,
finit en échec. Vérifié sur les trois VM : l'hôte rend bien « ODOO » pour les
deux qui écoutent, et le navigateur répondait 303 pendant que la colonne
disait « — ».
--- EN ---
The chained name becomes systematic, but entries written BEFORE carry the
short one — and nothing would retire them: they do not declare the name we
now write. Two blocks would lead to the same machine, exactly what we had
just removed.
The ProxyJump decides: a block hopping through THIS host is ours, so it goes.
A local namesake's has none, and is never touched; another Proxmox host's
neither.
The Odoo flag gains its test along the way. It failed for the same reason as
the empty columns — the probe is the remote pipeline's last link, and a fleet
where a single VM has no Odoo, a nested hypervisor for instance, ends in
failure. Verified on all three VMs: the host does return "ODOO" for the two
that listen, and the browser answered 303 while the column said "—".
Assisted-by: Claude Opus 5
2026-08-25 00:43:10 -04:00
|
|
|
|
also_drop=self._pve_alias_perime(
|
|
|
|
|
|
vm["name"], host["target"]
|
|
|
|
|
|
),
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
)
|
[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
|
|
|
|
alias[vm["name"]] = noms_alias[0]
|
|
|
|
|
|
print(f" ✓ ~/.ssh/config : ssh {noms_alias[0]}")
|
[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
|
|
|
|
vm["adresse"] = ip
|
|
|
|
|
|
vm["alias"] = alias.get(vm["name"], vm["name"])
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
# Une adresse n'est pas une machine prête : cloud-init tourne
|
|
|
|
|
|
# encore, et sshd n'écoute pas toujours. Les quatre étapes qui
|
|
|
|
|
|
# suivent passent TOUTES par ssh — sans cette attente, elles
|
|
|
|
|
|
# échouaient ensemble sur une VM qui n'avait pas fini de naître,
|
|
|
|
|
|
# et la machine partait sans guide, en UTC, sans autorité et sur
|
|
|
|
|
|
# le miroir de son image.
|
|
|
|
|
|
if vm["alias"] and not self._pve_attendre_ssh(vm["alias"]):
|
|
|
|
|
|
print(f" ⚠ {t('No ssh answer: guest left as created.')}")
|
|
|
|
|
|
vm["alias"] = ""
|
[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
|
|
|
|
# Le guide AVANT l'installation : il doit être là même si rien ne
|
|
|
|
|
|
# s'installe, et l'installation ne le touche pas.
|
|
|
|
|
|
if vm["alias"] and mod_qemu:
|
|
|
|
|
|
self._pve_write_guide(vm["alias"], vm, spec, mod_qemu)
|
[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
|
|
|
|
if vm["alias"]:
|
|
|
|
|
|
self._pve_set_timezone(vm["alias"], spec)
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
# Après le fuseau et avant l'installation : c'est
|
|
|
|
|
|
# l'installation qui télécharge.
|
|
|
|
|
|
if ca_cache:
|
|
|
|
|
|
# Le miroir AVANT l'autorité et l'installation : le cache
|
|
|
|
|
|
# range ses index sous l'hôte demandé, et une VM qui en
|
|
|
|
|
|
# réclame un autre ne retrouve rien de ce qui est gardé.
|
|
|
|
|
|
self._pve_set_apt_mirror(vm["alias"], vm, mod_qemu)
|
[FIX] cache et journal : hôte imbriqué soustrait, décision écrite
Un invité sans magasin de confiance ne peut RIEN recevoir, et sur un hôte
Proxmox c'est l'HÔTE qu'il faut excepter : imbriqué, l'invité sort masqué
derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis
l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le
gestionnaire de paquets se rabattait sur 564 dérivations à construire.
Le journal par VM ne portait AUCUN message du menu : la cause vivait sur
une console qui défile, et le fichier qu'on rouvre après l'échec n'avait
que le symptôme. Et « db_drop_all » annonçait détruites des bases qui ne
l'étaient pas, son code de retour jeté.
--- EN ---
A guest with no trust store can receive NOTHING, and on a Proxmox host it
is the HOST that must be exempted: nested, the guest leaves masqueraded
behind it and the bridge never sees its own address. Measured from the
guest: code 000 and SSL verification 19, then 200 and 0. Without it the
package manager fell back to building 564 derivations.
The per-VM log carried NO message from the menu: the cause lived on a
console that scrolls away, and the file reopened after failure held only
the symptom. And "db_drop_all" announced databases as dropped that were
not, its exit status discarded.
Assisted-by: Claude Opus 5
2026-09-16 00:43:40 -04:00
|
|
|
|
# Un invité dont le magasin de confiance n'a pas de forme
|
|
|
|
|
|
# par fichier ne peut RIEN recevoir : on soustrait son
|
|
|
|
|
|
# hôte au cache à la place. L'autorité n'est posée que
|
|
|
|
|
|
# lorsqu'il y a quelqu'un pour la recevoir.
|
[FIX] cache et guide : suivre la traduction du binaire et le hors ligne
Le rebase apporte trois choses auxquelles ce travail devait s'accorder.
Les messages du binaire se traduisent désormais : ceux du refus de place
et de l'aide de --oublie passent par T(), avec leur entrée au catalogue —
deux garde-fous refusent sinon un message sans traduction ou l'inverse.
Soustraire l'hôte au cache est REFUSÉ hors ligne : le magasin est alors la
seule source, et l'excepter ne le ferait pas télécharger en direct, cela
le priverait de tout, ses propres paquets compris.
Et le guide de connexion annonce les outils sur la voie Proxmox aussi ; il
n'y était plus écrit du tout, l'argument manquant étant avalé.
--- EN ---
The rebase brings three things this work had to align with. The binary's
messages are now translatable: the out-of-space refusal and the --oublie
help go through T(), with their catalogue entry — two guard rails
otherwise refuse a message without a translation, or the reverse.
Exempting the host from the cache is REFUSED offline: the store is then
the only source, and exempting it would not make it download directly, it
would leave it with nothing, its own packages included.
And the connection guide announces the tools on the Proxmox path too; it
was no longer written there at all, the missing argument being swallowed.
Assisted-by: Claude Opus 5
2026-09-16 05:58:59 -04:00
|
|
|
|
if not self._pve_cache_bypass_hote(
|
|
|
|
|
|
host, vm, hors_ligne=bool(coupee)
|
|
|
|
|
|
):
|
[FIX] cache et journal : hôte imbriqué soustrait, décision écrite
Un invité sans magasin de confiance ne peut RIEN recevoir, et sur un hôte
Proxmox c'est l'HÔTE qu'il faut excepter : imbriqué, l'invité sort masqué
derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis
l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le
gestionnaire de paquets se rabattait sur 564 dérivations à construire.
Le journal par VM ne portait AUCUN message du menu : la cause vivait sur
une console qui défile, et le fichier qu'on rouvre après l'échec n'avait
que le symptôme. Et « db_drop_all » annonçait détruites des bases qui ne
l'étaient pas, son code de retour jeté.
--- EN ---
A guest with no trust store can receive NOTHING, and on a Proxmox host it
is the HOST that must be exempted: nested, the guest leaves masqueraded
behind it and the bridge never sees its own address. Measured from the
guest: code 000 and SSL verification 19, then 200 and 0. Without it the
package manager fell back to building 564 derivations.
The per-VM log carried NO message from the menu: the cause lived on a
console that scrolls away, and the file reopened after failure held only
the symptom. And "db_drop_all" announced databases as dropped that were
not, its exit status discarded.
Assisted-by: Claude Opus 5
2026-09-16 00:43:40 -04:00
|
|
|
|
self._pve_set_cache_ca(
|
|
|
|
|
|
vm["alias"],
|
|
|
|
|
|
vm,
|
|
|
|
|
|
ca_cache,
|
|
|
|
|
|
hors_ligne=bool(coupee),
|
|
|
|
|
|
)
|
[ADD] proxmox deploy : accélération 3D, bibliothèques sondées et posées
A Proxmox VM always used the serial console as its display, so nothing inside
it could render. The form offers 3D: the VM gets « --vga virtio-gl » and the
account joins the render and video groups in the guest, which « qm set »
cannot write; without them GL falls back to software while VIRGL reports
success. The box is offered only where the host has a render node, VIRGL, GL
and EGL, Proxmox refusing to start the VM otherwise once its disk is written.
What is missing is named with its package, and a button installs it, then
reads the host again. Covered by test/test_proxmox_form.py.
--- FR ---
Une VM Proxmox prenait toujours la console série pour affichage : rien ne
pouvait y rendre. Le formulaire offre la 3D : la VM reçoit « --vga virtio-gl »
et le compte entre dans les groupes render et video dans l'invité, ce que
« qm set » ne sait pas écrire ; sans eux, GL retombe en logiciel alors que
VIRGL annonce un succès. La case n'est offerte que si l'hôte a un nœud de
rendu, VIRGL, GL et EGL, Proxmox refusant sinon de démarrer la VM une fois son
disque écrit. Ce qui manque est nommé avec son paquet, et un bouton le pose
puis relit l'hôte. Couvert par test/test_proxmox_form.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:20 -04:00
|
|
|
|
# Après la création, qui a posé l'écran accéléré : l'accès au
|
|
|
|
|
|
# nœud de rendu est une affaire de COMPTE, et il se donne
|
|
|
|
|
|
# dans l'invité.
|
|
|
|
|
|
if spec.get("gpu3d"):
|
|
|
|
|
|
self._pve_set_gpu_groups(
|
|
|
|
|
|
vm["alias"],
|
|
|
|
|
|
spec.get("user") or "erplibre",
|
|
|
|
|
|
mod_qemu,
|
|
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
joignables.append(vm)
|
|
|
|
|
|
install = spec.get("install")
|
[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
|
|
|
|
# Rendu à l'appelant pour son sommaire : lui seul sait ce qui a été
|
|
|
|
|
|
# RÉELLEMENT joint.
|
|
|
|
|
|
resultat = list(joignables)
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
# Le suivi vient du DÉPLOIEMENT, pas de l'installation — même règle
|
|
|
|
|
|
# qu'en QEMU/KVM. Sans elle, la case « Suivre l'installation » ne
|
|
|
|
|
|
# commandait rien : décochée, le tableau de bord s'ouvrait quand
|
|
|
|
|
|
# même ; cochée sans rien à installer, il ne s'ouvrait jamais.
|
|
|
|
|
|
suivi = spec.get("monitor", True)
|
|
|
|
|
|
if not joignables or not (install or suivi):
|
[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
|
|
|
|
return resultat
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
noms = [vm["name"] for vm in joignables]
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
if install:
|
|
|
|
|
|
print(f" {install.get('label') or ''}")
|
[FIX] qemu : ne pas poser ERPLibre sur une VM Proxmox VE
Choisir « Proxmox VE » comme système, c'est demander qu'il soit installé.
L'invite en ligne le savait ; le formulaire posait « ERPLibre + Odoo 18 »,
lui ajoutait les 5 Go réservés au dépôt et annonçait une cible make dans le
guide de la VM. La règle vit maintenant en un seul endroit, les deux
chemins la lisent, et un choix explicite l'emporte toujours sur elle.
Trois défauts trouvés derrière. Une commande par VM n'était retenue que si
DEUX VM différaient : déployée seule, la VM Proxmox retombait sur la
commande commune. Le disque choisi à la main disparaissait quand rien
n'était installé — 60 G demandés, 20 G créés. Et une taille absente des
préréglages marquait la rangée ✎ dès son montage.
--- EN ---
Choosing "Proxmox VE" as the system means asking for it to be installed.
The command-line prompt knew that; the form set "ERPLibre + Odoo 18", added
the 5 GB meant for the repository and advertised a make target in the VM's
guide. The rule now lives in one place, both paths read it, and an explicit
choice always wins over it.
Three defects behind it. A per-VM command was only used when TWO VMs
differed: deployed alone, the Proxmox VM fell back to the common command.
A hand-picked disk size vanished when nothing was installed — 60 G asked,
20 G created. And a size absent from the presets marked its row ✎ on mount.
Assisted-by: Claude Opus 5
2026-08-24 01:34:10 -04:00
|
|
|
|
# Une commande PAR VM dès qu'elles diffèrent : un Proxmox imbriqué
|
|
|
|
|
|
# installe son hyperviseur, ses voisines ERPLibre. Une commande
|
|
|
|
|
|
# unique en aurait imposé une aux deux.
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
commun = (install or {}).get("cmd") or ""
|
[FIX] qemu : ne pas poser ERPLibre sur une VM Proxmox VE
Choisir « Proxmox VE » comme système, c'est demander qu'il soit installé.
L'invite en ligne le savait ; le formulaire posait « ERPLibre + Odoo 18 »,
lui ajoutait les 5 Go réservés au dépôt et annonçait une cible make dans le
guide de la VM. La règle vit maintenant en un seul endroit, les deux
chemins la lisent, et un choix explicite l'emporte toujours sur elle.
Trois défauts trouvés derrière. Une commande par VM n'était retenue que si
DEUX VM différaient : déployée seule, la VM Proxmox retombait sur la
commande commune. Le disque choisi à la main disparaissait quand rien
n'était installé — 60 G demandés, 20 G créés. Et une taille absente des
préréglages marquait la rangée ✎ dès son montage.
--- EN ---
Choosing "Proxmox VE" as the system means asking for it to be installed.
The command-line prompt knew that; the form set "ERPLibre + Odoo 18", added
the 5 GB meant for the repository and advertised a make target in the VM's
guide. The rule now lives in one place, both paths read it, and an explicit
choice always wins over it.
Three defects behind it. A per-VM command was only used when TWO VMs
differed: deployed alone, the Proxmox VM fell back to the common command.
A hand-picked disk size vanished when nothing was installed — 60 G asked,
20 G created. And a size absent from the presets marked its row ✎ on mount.
Assisted-by: Claude Opus 5
2026-08-24 01:34:10 -04:00
|
|
|
|
cartes = {
|
|
|
|
|
|
vm["name"]: (vm.get("install_cmd") or commun) for vm in joignables
|
|
|
|
|
|
}
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
finale = cartes if self._qemu_per_vm(cartes, commun) else commun
|
|
|
|
|
|
branche = (install or {}).get("branch") or ""
|
[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
|
|
|
|
# Même règle pour la branche et pour le type de VM : depuis que le
|
|
|
|
|
|
# plan les porte PAR RANGÉE, lire la seule valeur commune revenait à
|
|
|
|
|
|
# jeter le choix. Un parc mixte — un hyperviseur imbriqué à côté de VM
|
|
|
|
|
|
# ERPLibre — est justement ce qu'on déploie ici le plus souvent.
|
|
|
|
|
|
branches_vm = {
|
|
|
|
|
|
vm["name"]: (vm.get("branch") or branche) for vm in joignables
|
|
|
|
|
|
}
|
|
|
|
|
|
if self._qemu_per_vm(branches_vm, branche):
|
|
|
|
|
|
branche = branches_vm
|
|
|
|
|
|
bureau = spec.get("desktop") or ""
|
|
|
|
|
|
bureaux = {
|
|
|
|
|
|
vm["name"]: (vm.get("desktop") or bureau) for vm in joignables
|
|
|
|
|
|
}
|
|
|
|
|
|
if self._qemu_per_vm(bureaux, bureau):
|
|
|
|
|
|
bureau = bureaux
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
if suivi:
|
|
|
|
|
|
# Rien à installer ? La commande distante regarde alors la VM
|
|
|
|
|
|
# ARRIVER (cloud-init, puis relevé système) : c'est ce que le
|
|
|
|
|
|
# tableau de bord montre.
|
[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
|
|
|
|
#
|
|
|
|
|
|
# La carte des hôtes suit : sans elle, les colonnes vivantes
|
|
|
|
|
|
# (état, durée, écrit/s, RAM, disque) restaient VIDES pour une VM
|
|
|
|
|
|
# posée sur un Proxmox distant — elles viennent de virsh, qui ne
|
|
|
|
|
|
# connaît pas cet hôte.
|
|
|
|
|
|
cartes_pve = {
|
|
|
|
|
|
vm["name"]: {
|
|
|
|
|
|
"target": host["target"],
|
|
|
|
|
|
"sudo": host.get("sudo") or "",
|
|
|
|
|
|
"jump": host.get("jump") or "",
|
|
|
|
|
|
"vmid": vm.get("vmid"),
|
[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
|
|
|
|
# L'adresse INTERNE : elle n'est pas routable d'ici, mais
|
|
|
|
|
|
# elle l'est depuis l'hôte. Avec le rebond, le tableau de
|
|
|
|
|
|
# bord entre dans la VM sans dépendre de ~/.ssh/config.
|
|
|
|
|
|
"addr": vm.get("adresse") or "",
|
[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
|
|
|
|
}
|
|
|
|
|
|
for vm in joignables
|
|
|
|
|
|
if vm.get("vmid")
|
|
|
|
|
|
}
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
self._qemu_install_erplibre_monitored(
|
[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
|
|
|
|
noms,
|
|
|
|
|
|
branche,
|
[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
|
|
|
|
{n: alias.get(n, n) for n in noms},
|
[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
|
|
|
|
finale,
|
[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
|
|
|
|
# Les réglages du système invité, qui n'atteignaient pas la
|
|
|
|
|
|
# commande distante : la VM naissait serveur nu, sans outils.
|
|
|
|
|
|
prod=bool(spec.get("prod")),
|
[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
|
|
|
|
desktop=bureau,
|
[FIX] suivi : pas de poubelle avant d'en être sûr, et mise sur Proxmox
Rapporté : une VM Arch à peine déployée sur Proxmox s'affichait 🗑 dès le
premier tour. « Effacée » est un état TERMINAL — la ligne gèle et ne revient
jamais — et il se déduisait d'UN relevé manquant. Or l'hôte peut être occupé,
la VM en train de naître, le relevé en cache d'avant sa création. On distingue
désormais « l'hôte n'a pas répondu » (on ne sait rien) de « l'hôte a répondu
sans elle » (on compte, trois fois), et la case part de « - » plutôt que d'un
sablier qui affirmerait qu'on attend quelque chose.
L'écran Proxmox n'offrait pas le choix de l'interpréteur Python : il envoyait
donc toujours « automatique », et comme mise n'est jamais installé d'office,
c'était pyenv — qui COMPILE Python depuis le tar.xz. Le choix existe
maintenant des deux côtés, avec le même garde-fou : rien n'est imposé quand
aucune architecture retenue n'est servie par mise.
--- EN ---
Reported: an Arch VM barely deployed on Proxmox showed 🗑 on the very first
pass. "Deleted" is a TERMINAL state — the row freezes and never comes back —
and it was inferred from ONE missing reading. Yet the host may be busy, the VM
may be starting, the reading may be cached from before it existed. We now tell
"the host did not answer" (we know nothing) from "the host answered without
it" (count, three times), and the cell starts at "-" rather than an hourglass
claiming we await something.
The Proxmox screen offered no Python interpreter choice: it therefore always
sent "automatic", and since mise is never installed by default, that meant
pyenv — which COMPILES Python from the tar.xz. The choice now exists on both
sides, with the same guard: nothing is imposed when no selected architecture
is served by mise.
Assisted-by: Claude Opus 5
2026-08-24 07:53:54 -04:00
|
|
|
|
python_provider=spec.get("python_provider") or "",
|
[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
|
|
|
|
app_store=spec.get("app_store") or "deb",
|
|
|
|
|
|
vm_tools=spec.get("vm_tools") or (),
|
[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
|
|
|
|
pve=cartes_pve,
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
|
guet_hors_ligne=coupee,
|
|
|
|
|
|
deploy_started=debut,
|
|
|
|
|
|
# La coupure TENUE, et non la case de la spec : c'est elle
|
|
|
|
|
|
# qui fait qu'une réussite prouve le hors ligne.
|
|
|
|
|
|
hors_ligne=bool(coupee),
|
[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
|
|
|
|
# Ce que sont ces VM, pris de la SPEC. Le suivi le demandait
|
|
|
|
|
|
# à virsh, qui ne connaît que les domaines d'ici.
|
|
|
|
|
|
meta={
|
|
|
|
|
|
vm["name"]: (
|
|
|
|
|
|
vm.get("distro"),
|
|
|
|
|
|
vm.get("version"),
|
|
|
|
|
|
vm.get("arch") or "amd64",
|
|
|
|
|
|
)
|
|
|
|
|
|
for vm in joignables
|
|
|
|
|
|
},
|
[FIX] cache et journal : hôte imbriqué soustrait, décision écrite
Un invité sans magasin de confiance ne peut RIEN recevoir, et sur un hôte
Proxmox c'est l'HÔTE qu'il faut excepter : imbriqué, l'invité sort masqué
derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis
l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le
gestionnaire de paquets se rabattait sur 564 dérivations à construire.
Le journal par VM ne portait AUCUN message du menu : la cause vivait sur
une console qui défile, et le fichier qu'on rouvre après l'échec n'avait
que le symptôme. Et « db_drop_all » annonçait détruites des bases qui ne
l'étaient pas, son code de retour jeté.
--- EN ---
A guest with no trust store can receive NOTHING, and on a Proxmox host it
is the HOST that must be exempted: nested, the guest leaves masqueraded
behind it and the bridge never sees its own address. Measured from the
guest: code 000 and SSL verification 19, then 200 and 0. Without it the
package manager fell back to building 564 derivations.
The per-VM log carried NO message from the menu: the cause lived on a
console that scrolls away, and the file reopened after failure held only
the symptom. And "db_drop_all" announced databases as dropped that were
not, its exit status discarded.
Assisted-by: Claude Opus 5
2026-09-16 00:43:40 -04:00
|
|
|
|
# Ce que l'hôte a DÉCIDÉ pour chaque VM avant de lancer
|
|
|
|
|
|
# l'installation. Sans cela, le journal ne porte que le
|
|
|
|
|
|
# symptôme, et la cause reste sur une console qui défile.
|
|
|
|
|
|
notes={
|
|
|
|
|
|
vm["name"]: vm.get("notes") or []
|
|
|
|
|
|
for vm in joignables
|
|
|
|
|
|
if vm.get("notes")
|
|
|
|
|
|
},
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
)
|
[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
|
|
|
|
return resultat
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
# Sans suivi mais avec quelque chose à installer : en série, sortie à
|
|
|
|
|
|
# l'écran. C'est le pendant exact de la voie QEMU/KVM.
|
[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
|
|
|
|
etiquette = branche if isinstance(branche, str) else t("per VM")
|
|
|
|
|
|
print(f"\n{t('Installing ERPLibre on each VM')} ({etiquette})…")
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
for vm in joignables:
|
|
|
|
|
|
self._qemu_install_erplibre_vm(
|
|
|
|
|
|
vm["name"],
|
|
|
|
|
|
cle_locale,
|
[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
|
|
|
|
branches_vm.get(vm["name"], ""),
|
[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
|
|
|
|
alias.get(vm["name"], vm["name"]),
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
vm.get("install_cmd") or commun,
|
[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
|
|
|
|
bool(spec.get("prod")),
|
[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
|
|
|
|
desktop=bureaux.get(vm["name"], ""),
|
[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
|
|
|
|
python_provider=spec.get("python_provider") or "",
|
|
|
|
|
|
app_store=spec.get("app_store") or "deb",
|
|
|
|
|
|
vm_tools=spec.get("vm_tools") or (),
|
[FIX] proxmox : sh: 1: Syntax error: "(" unexpected
Rapporté. La chaîne, en trois maillons : sur un hôte sans pont, « ip -o link
show type bridge » ne rend RIEN, la sortie ne contient donc que
l'avertissement de ssh sur la clé d'hôte — que le lecteur a pris pour un nom
de pont. « (ED25519) » s'est retrouvé dans « --net0 virtio,bridge=… », enrobé
de « sudo sh -c », et dash a répondu ce que l'utilisateur a lu. Le bruit de
ssh est maintenant retiré à la source, et un pont doit avoir la forme d'un
lien pour en être un.
Éprouvé sur l'hôte réel, VM créée puis détruite : le pont ne montait pas
(ifupdown2 accuse « another instance » quand /run/network manque — un
mensonge), le noyau Debian n'a ni module bridge ni table NAT, et une VM en
adresse fixe n'avait aucun résolveur. Le déploiement écrit désormais un
journal par VM sous ~/.erplibre/proxmox-deploy et en donne le chemin.
--- EN ---
Reported. The chain, in three links: on a host with no bridge, "ip -o link
show type bridge" returns NOTHING, so the output holds only ssh's host-key
warning — which the parser took for a bridge name. "(ED25519)" landed in
"--net0 virtio,bridge=…", wrapped in "sudo sh -c", and dash answered what the
user read. Ssh's noise is now stripped at the source, and a bridge must have
the shape of a link to be one.
Proven on the real host, VM created then destroyed: the bridge would not come
up (ifupdown2 claims "another instance" when /run/network is missing — a lie),
the Debian kernel has neither the bridge module nor the NAT table, and a
statically addressed VM had no resolver at all. Deployment now writes one log
per VM under ~/.erplibre/proxmox-deploy and prints its path.
Assisted-by: Claude Opus 5
2026-08-24 04:17:36 -04:00
|
|
|
|
)
|
[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
|
|
|
|
return resultat
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
|
|
|
|
|
|
def _pve_deploy_prompts(self, dry_run=False):
|
|
|
|
|
|
"""Déploie une VM SUR l'hôte Proxmox choisi, par questions.
|
|
|
|
|
|
|
|
|
|
|
|
Le catalogue d'images est celui du dépôt (le même que QEMU/KVM) : c'est
|
|
|
|
|
|
une connaissance locale, indépendante de l'hyperviseur. Tout le reste
|
|
|
|
|
|
part sur l'hôte — téléchargement compris, puisque c'est là que le
|
|
|
|
|
|
disque sera écrit.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
host = self._pve_host()
|
|
|
|
|
|
if not host:
|
|
|
|
|
|
return
|
|
|
|
|
|
mod = self._qemu_import_module()
|
|
|
|
|
|
distro = self._qemu_prompt_distro()
|
|
|
|
|
|
version = self._qemu_prompt_version(distro)
|
|
|
|
|
|
arch = "amd64"
|
[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
|
|
|
|
nom = (
|
|
|
|
|
|
input(t("VM name (default: erplibre-<distro>): ")).strip()
|
|
|
|
|
|
or f"erplibre-{distro}"
|
|
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
memoire = (
|
|
|
|
|
|
self._qemu_ask_ram(t("RAM in MB, blank = 4096"), 4096) or 4096
|
|
|
|
|
|
)
|
|
|
|
|
|
vcpus = (
|
|
|
|
|
|
self._qemu_ask_cpu(t("vCPU, blank = 2"), 2, os.cpu_count() or 2)
|
|
|
|
|
|
or 2
|
|
|
|
|
|
)
|
|
|
|
|
|
disque = input(t("Disk size (default 32G): ")).strip() or "32G"
|
|
|
|
|
|
|
|
|
|
|
|
code, _v = mod.DISTROS[distro][0][version][:2]
|
|
|
|
|
|
url = mod.image_url(distro, code, arch, version)
|
|
|
|
|
|
image = mod.default_image_name(distro, code, arch, version)
|
|
|
|
|
|
|
|
|
|
|
|
# Stockage et pont : demandés à l'HÔTE, jamais devinés. « local-lvm »
|
|
|
|
|
|
# n'existe pas partout, et un pont inventé fait échouer « qm create ».
|
|
|
|
|
|
_c, out = self._pve_show("pvesm status --content images", quiet=True)
|
|
|
|
|
|
stockages = pve.parse_storages(out)
|
|
|
|
|
|
_c, out = self._pve_show("ip -o link show type bridge", quiet=True)
|
|
|
|
|
|
ponts = pve.parse_bridges(out)
|
|
|
|
|
|
_c, cfg_reseau = self._pve_show(
|
|
|
|
|
|
"cat /etc/network/interfaces", quiet=True
|
|
|
|
|
|
)
|
|
|
|
|
|
infos_ponts = pve.parse_bridge_config(cfg_reseau)
|
|
|
|
|
|
stockage = pve.pick_storage(stockages)
|
|
|
|
|
|
pont = pve.pick_bridge(ponts)
|
|
|
|
|
|
if not stockage:
|
|
|
|
|
|
print(f"\n ✗ {t('No storage able to hold a VM disk.')}")
|
[ADD] proxmox : l'écran remet pmxcfs debout lui-même
Le conseil « rejouer install_proxmox.sh sur l'hôte » ne pouvait PAS marcher :
la VM clone le dépôt distant, donc sa copie du script est celle du distant —
tant que le correctif n'y est pas, celle qui ne corrige rien. Trois hôtes de
suite sont tombés dessus, avec le même message inutile. L'écran répare donc :
gel de cloud-init, réécriture de /etc/hosts, relance des unités, constat du
montage — le pendant exact de l'offre de créer un pont.
Écrit, puis ATTAQUÉ par trois lentilles sur le code réel. Ce qu'elles ont
mesuré valait la peine.
/etc/hosts se réécrivait en DEUX écritures — « sed -i » puis « printf >> » —
alors que la docstring promettait l'inverse. Sed refusé et ajout réussi, la
ligne 127.0.1.1 survivait EN PREMIER et la nôtre s'ajoutait une fois par
tentative ; sed réussi et ajout refusé, l'hôte perdait l'entrée de son nom, et
chaque sudo y attendait ensuite le résolveur. C'est maintenant un fichier
complet bâti dans un temporaire, VÉRIFIÉ, puis recopié — « cat > » et non
« mv », qui remplacerait l'inode et perdrait mode et propriétaire.
Le contrôle final s'en remettait à « getent hosts », qui réussit via mDNS même
quand rien n'a été écrit — et acceptait les fe80:: que notre propre code
rejette. Il relit désormais ce qui a été écrit.
awk remplace sed pour filtrer : « print » émet un saut de ligne, donc un
/etc/hosts non terminé par un — cloud-init n'en met pas — est normalisé. Sans
ça notre ligne se collait à la précédente et le nom du nœud partait sur
l'adresse d'une autre machine.
Trois autres, du même acabit. Les dépendants de pmxcfs sont relancés eux
aussi : actifs pendant la panne, ils échouaient sur ipcc_send_rec, et les
laisser donnait une GUI en « communication failure » juste après notre ✓. Un
silence du lien n'est plus lu comme une absence de montage. Et l'adresse n'est
mise en cause que si pve-cluster a réellement démarré.
Les tests exécutent les commandes au lieu de les relire, bouchons capables
d'ÉCHOUER : écriture refusée, fichier sans saut de ligne final, tabulations,
start qui rate, montage qui disparaît pendant la reconfirmation. Prouvé par
mutation — trois HOSTS-KO changés en HOSTS-OK font rougir le test.
--- EN ---
The advice "replay install_proxmox.sh on the host" could NOT work: the VM
clones the remote, so its copy of the script is the remote's — while the fix
is not there, the one that fixes nothing. Three hosts in a row hit it with the
same useless message. So the screen repairs: freeze cloud-init, rewrite
/etc/hosts, restart the units, verify the mount — the exact counterpart of the
offer to create a bridge.
Written, then ATTACKED by three lenses on the real code. What they measured
was worth it.
/etc/hosts was rewritten in TWO writes — "sed -i" then "printf >>" — while the
docstring promised the opposite. Sed refused and append succeeded: the
127.0.1.1 line survived FIRST and ours was added once per attempt; sed
succeeded and append refused: the host lost its own name entry, and every sudo
then waited on the resolver. It is now a complete file built in a temporary,
VERIFIED, then copied over — "cat >" not "mv", which would replace the inode
and lose mode and owner.
The final check relied on "getent hosts", which succeeds via mDNS even when
nothing was written — and accepted the fe80:: our own code rejects. It now
re-reads what was written.
awk replaces sed for filtering: "print" emits a newline, so an /etc/hosts with
no final one — cloud-init omits it — gets normalised. Without that our line
glued onto the previous one and the node's name pointed at another machine's
address.
Three more of the same kind. pmxcfs's dependents are restarted too: active
throughout the outage, they failed on ipcc_send_rec, and leaving them gave a
GUI in "communication failure" right after our ✓. A silent link is no longer
read as a missing mount. And the address is only blamed if pve-cluster
actually started.
The tests execute the commands instead of reading them, with stubs able to
FAIL: refused write, file with no final newline, tabs, a start that fails, a
mount that vanishes during reconfirmation. Proven by mutation — three
HOSTS-KO turned into HOSTS-OK make the test go red.
Assisted-by: Claude Opus 5
(cherry picked from commit d4f9358c6cb562029cc2ca9eb478d80c6a0a19a4)
2026-08-26 03:19:58 -04:00
|
|
|
|
# Le symptôme ne suffit pas : dire la CAUSE quand on la connaît,
|
|
|
|
|
|
# puis proposer d'y remédier. Une seule sonde pour les deux.
|
|
|
|
|
|
etat_pve, quoi_pve = self._pve_cluster_state(host)
|
|
|
|
|
|
for ligne in self._pve_cluster_reason(host, etat_pve, quoi_pve):
|
[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
|
|
|
|
print(f" {ligne}")
|
[ADD] proxmox : l'écran remet pmxcfs debout lui-même
Le conseil « rejouer install_proxmox.sh sur l'hôte » ne pouvait PAS marcher :
la VM clone le dépôt distant, donc sa copie du script est celle du distant —
tant que le correctif n'y est pas, celle qui ne corrige rien. Trois hôtes de
suite sont tombés dessus, avec le même message inutile. L'écran répare donc :
gel de cloud-init, réécriture de /etc/hosts, relance des unités, constat du
montage — le pendant exact de l'offre de créer un pont.
Écrit, puis ATTAQUÉ par trois lentilles sur le code réel. Ce qu'elles ont
mesuré valait la peine.
/etc/hosts se réécrivait en DEUX écritures — « sed -i » puis « printf >> » —
alors que la docstring promettait l'inverse. Sed refusé et ajout réussi, la
ligne 127.0.1.1 survivait EN PREMIER et la nôtre s'ajoutait une fois par
tentative ; sed réussi et ajout refusé, l'hôte perdait l'entrée de son nom, et
chaque sudo y attendait ensuite le résolveur. C'est maintenant un fichier
complet bâti dans un temporaire, VÉRIFIÉ, puis recopié — « cat > » et non
« mv », qui remplacerait l'inode et perdrait mode et propriétaire.
Le contrôle final s'en remettait à « getent hosts », qui réussit via mDNS même
quand rien n'a été écrit — et acceptait les fe80:: que notre propre code
rejette. Il relit désormais ce qui a été écrit.
awk remplace sed pour filtrer : « print » émet un saut de ligne, donc un
/etc/hosts non terminé par un — cloud-init n'en met pas — est normalisé. Sans
ça notre ligne se collait à la précédente et le nom du nœud partait sur
l'adresse d'une autre machine.
Trois autres, du même acabit. Les dépendants de pmxcfs sont relancés eux
aussi : actifs pendant la panne, ils échouaient sur ipcc_send_rec, et les
laisser donnait une GUI en « communication failure » juste après notre ✓. Un
silence du lien n'est plus lu comme une absence de montage. Et l'adresse n'est
mise en cause que si pve-cluster a réellement démarré.
Les tests exécutent les commandes au lieu de les relire, bouchons capables
d'ÉCHOUER : écriture refusée, fichier sans saut de ligne final, tabulations,
start qui rate, montage qui disparaît pendant la reconfirmation. Prouvé par
mutation — trois HOSTS-KO changés en HOSTS-OK font rougir le test.
--- EN ---
The advice "replay install_proxmox.sh on the host" could NOT work: the VM
clones the remote, so its copy of the script is the remote's — while the fix
is not there, the one that fixes nothing. Three hosts in a row hit it with the
same useless message. So the screen repairs: freeze cloud-init, rewrite
/etc/hosts, restart the units, verify the mount — the exact counterpart of the
offer to create a bridge.
Written, then ATTACKED by three lenses on the real code. What they measured
was worth it.
/etc/hosts was rewritten in TWO writes — "sed -i" then "printf >>" — while the
docstring promised the opposite. Sed refused and append succeeded: the
127.0.1.1 line survived FIRST and ours was added once per attempt; sed
succeeded and append refused: the host lost its own name entry, and every sudo
then waited on the resolver. It is now a complete file built in a temporary,
VERIFIED, then copied over — "cat >" not "mv", which would replace the inode
and lose mode and owner.
The final check relied on "getent hosts", which succeeds via mDNS even when
nothing was written — and accepted the fe80:: our own code rejects. It now
re-reads what was written.
awk replaces sed for filtering: "print" emits a newline, so an /etc/hosts with
no final one — cloud-init omits it — gets normalised. Without that our line
glued onto the previous one and the node's name pointed at another machine's
address.
Three more of the same kind. pmxcfs's dependents are restarted too: active
throughout the outage, they failed on ipcc_send_rec, and leaving them gave a
GUI in "communication failure" right after our ✓. A silent link is no longer
read as a missing mount. And the address is only blamed if pve-cluster
actually started.
The tests execute the commands instead of reading them, with stubs able to
FAIL: refused write, file with no final newline, tabs, a start that fails, a
mount that vanishes during reconfirmation. Proven by mutation — three
HOSTS-KO turned into HOSTS-OK make the test go red.
Assisted-by: Claude Opus 5
(cherry picked from commit d4f9358c6cb562029cc2ca9eb478d80c6a0a19a4)
2026-08-26 03:19:58 -04:00
|
|
|
|
if not self._pve_offer_cluster_fix(host, etat_pve, quoi_pve):
|
|
|
|
|
|
return
|
|
|
|
|
|
_c, out = self._pve_show(
|
|
|
|
|
|
"pvesm status --content images", quiet=True
|
|
|
|
|
|
)
|
|
|
|
|
|
stockage = pve.pick_storage(pve.parse_storages(out))
|
|
|
|
|
|
if not stockage:
|
|
|
|
|
|
print(f" ✗ {t('No storage able to hold a VM disk.')}")
|
|
|
|
|
|
return
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
if not pont and not dry_run:
|
|
|
|
|
|
pont = self._pve_offer_bridge()
|
|
|
|
|
|
if not pont:
|
|
|
|
|
|
return
|
|
|
|
|
|
_c, cfg_reseau = self._pve_show(
|
|
|
|
|
|
"cat /etc/network/interfaces", quiet=True
|
|
|
|
|
|
)
|
|
|
|
|
|
infos_ponts = pve.parse_bridge_config(cfg_reseau)
|
|
|
|
|
|
elif not pont:
|
|
|
|
|
|
pont = pve.INTERNAL_BRIDGE
|
|
|
|
|
|
# Le VMID D'ABORD : l'adresse d'un pont interne s'en déduit, et
|
|
|
|
|
|
# l'afficher avant de l'avoir choisi ne pouvait pas marcher.
|
|
|
|
|
|
vmid = pve.next_vmid(self._pve_vms())
|
|
|
|
|
|
ipconfig = pve.ipconfig_for(infos_ponts.get(pont, {}), vmid)
|
[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
|
|
|
|
print(
|
|
|
|
|
|
f"\n {t('storage')} : {stockage} ({len(stockages)} {t('offered')})"
|
|
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
print(f" {t('bridge')} : {pont}")
|
|
|
|
|
|
print(f" {t('address')} {ipconfig}")
|
|
|
|
|
|
print(f" VMID : {vmid}")
|
|
|
|
|
|
|
|
|
|
|
|
cle_locale = self._qemu_default_ssh_key()
|
|
|
|
|
|
spec = {
|
|
|
|
|
|
"name": nom,
|
|
|
|
|
|
"memory": memoire,
|
|
|
|
|
|
"vcpus": vcpus,
|
|
|
|
|
|
"disk": disque,
|
|
|
|
|
|
"storage": stockage,
|
|
|
|
|
|
"bridge": pont,
|
|
|
|
|
|
"image": image,
|
2026-09-16 00:42:31 -04:00
|
|
|
|
"uefi": mod.requiert_uefi(distro),
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
"user": "erplibre",
|
|
|
|
|
|
"sshkey_path": "/root/.ssh/erplibre-deploy.pub",
|
2026-09-16 00:42:31 -04:00
|
|
|
|
# Le user-data du dépôt, pour TOUTES les distributions : un seul
|
|
|
|
|
|
# cloud-init à comprendre, et celui-là est déjà éprouvé.
|
|
|
|
|
|
"user_data": self._pve_user_data(mod, distro, nom, cle_locale),
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
"start": True,
|
|
|
|
|
|
# DHCP sur un pont qui donne sur le LAN, adresse FIXE sur un pont
|
|
|
|
|
|
# interne : là, aucun serveur DHCP ne répondrait et la VM
|
|
|
|
|
|
# resterait muette.
|
|
|
|
|
|
"ipconfig": ipconfig,
|
|
|
|
|
|
}
|
2026-09-16 00:42:31 -04:00
|
|
|
|
somme = mod.pinned_sha256(distro)
|
|
|
|
|
|
etapes = [
|
|
|
|
|
|
pve.image_fetch_cmd(url, image, sha256=somme)
|
|
|
|
|
|
] + pve.create_cmds(vmid, spec)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
if dry_run:
|
|
|
|
|
|
print(f"\n── {t('Would run on')} {host['target']} ──")
|
|
|
|
|
|
print(f" # {t('SSH key ->')} {spec['sshkey_path']}")
|
|
|
|
|
|
for cmd in etapes:
|
|
|
|
|
|
print(f" {cmd}")
|
|
|
|
|
|
return
|
|
|
|
|
|
if not self._is_yes_default_yes(
|
|
|
|
|
|
input(f"\n{t('Deploy this VM now? (Y/n): ')}")
|
|
|
|
|
|
):
|
|
|
|
|
|
print(t("Cancelled."))
|
|
|
|
|
|
return
|
|
|
|
|
|
if cle_locale and not self._pve_push_key(cle_locale):
|
|
|
|
|
|
print(f" ⚠ {t('SSH key not pushed: password login only.')}")
|
|
|
|
|
|
spec.pop("sshkey_path", None)
|
2026-09-16 00:42:31 -04:00
|
|
|
|
etapes = [
|
|
|
|
|
|
pve.image_fetch_cmd(url, image, sha256=somme)
|
|
|
|
|
|
] + pve.create_cmds(vmid, spec)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
for cmd in etapes:
|
|
|
|
|
|
code, _out = self._pve_show(cmd, timeout=1800)
|
|
|
|
|
|
if code:
|
|
|
|
|
|
print(f"\n ✗ {t('Step failed, stopping here.')}")
|
|
|
|
|
|
return
|
|
|
|
|
|
# Adresse fixe : c'est nous qui l'avons donnée, inutile de la
|
|
|
|
|
|
# chercher. La découverte ne sert qu'au DHCP.
|
|
|
|
|
|
ip = pve.ip_from_ipconfig(ipconfig)
|
|
|
|
|
|
if ip:
|
|
|
|
|
|
print(f"\n {t('address given at creation:')} {ip}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f"\n {t('Waiting for the VM address…')}")
|
|
|
|
|
|
ip = self._pve_guest_ip(vmid)
|
|
|
|
|
|
if not ip:
|
|
|
|
|
|
print(f" ⚠ {t('No address yet. Try [6] later.')}")
|
|
|
|
|
|
return
|
[FIX] proxmox : six défauts trouvés par un audit, pas à l'usage
Trois autres chemins menaient au 🗑 sur un seul incident, et « effacée » gèle
la ligne pour de bon. Un « virsh list » en échec condamnait TOUT le parc
local. Un statut Proxmox hors des trois attendus — prelaunch, suspended,
internal-error — passait pour une disparition. Et le code de sortie de la
suite distante est celui de son DERNIER maillon : un pvesh en panne se lisait
« l'hôte a répondu sans elle ». Ce qui prouve une réponse, c'est désormais une
liste de ressources analysable.
Le plan annonçait « 25G » quand « qm resize » recevait 20 : la marge
d'ERPLibre se perdait en route, la VM naissait trop petite. « Changer l'état »
choisissait par NOM, or seul le VMID est unique sur un hôte — cocher une VM
en éteignait deux homonymes. L'entrée 13 volait son alias à une VM locale du
même nom. Enfin le déploiement par QUESTIONS avait vieilli seul : il partage
maintenant l'épilogue de l'écran, donc le guide, l'alias protégé, les colonnes
vivantes et le sommaire.
--- EN ---
Three more paths led to 🗑 on a single incident, and "deleted" freezes the row
for good. One failing "virsh list" condemned the WHOLE local fleet. A Proxmox
status outside the three expected ones — prelaunch, suspended, internal-error
— passed for a disappearance. And a remote pipeline's exit code is its LAST
link's: a broken pvesh read as "the host answered without it". Proof of an
answer is now a parsable resource list.
The plan announced "25G" while "qm resize" got 20: ERPLibre's margin was lost
on the way and the VM was born too small. "Change state" selected by NAME,
yet only the VMID is unique on a host — ticking one VM shut down two
namesakes. Menu entry 13 stole its alias from a local VM of the same name.
Finally the QUESTION-driven deployment had aged alone: it now shares the
screen's epilogue — guide, protected alias, live columns and summary.
Assisted-by: Claude Opus 5
2026-08-24 14:15:03 -04:00
|
|
|
|
# ÉPILOGUE COMMUN avec l'écran, au lieu de le redire ici : cette voie
|
|
|
|
|
|
# avait vieilli en silence — pas de protection de l'alias contre un
|
|
|
|
|
|
# domaine local homonyme, pas de guide de connexion, pas de bloc
|
|
|
|
|
|
# « pve » (donc aucune colonne vivante dans le suivi), pas de
|
|
|
|
|
|
# sommaire. Trouvé par l'audit, jamais à l'usage.
|
|
|
|
|
|
install = None
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
if self._is_yes_default_yes(
|
|
|
|
|
|
input(f"\n{t('Install ERPLibre on it? (Y/n): ')}")
|
|
|
|
|
|
):
|
|
|
|
|
|
branch = self._qemu_pick_branch()
|
|
|
|
|
|
label, cmd = self._qemu_pick_install_profile(distro)
|
|
|
|
|
|
print(f" {label}")
|
[FIX] proxmox : six défauts trouvés par un audit, pas à l'usage
Trois autres chemins menaient au 🗑 sur un seul incident, et « effacée » gèle
la ligne pour de bon. Un « virsh list » en échec condamnait TOUT le parc
local. Un statut Proxmox hors des trois attendus — prelaunch, suspended,
internal-error — passait pour une disparition. Et le code de sortie de la
suite distante est celui de son DERNIER maillon : un pvesh en panne se lisait
« l'hôte a répondu sans elle ». Ce qui prouve une réponse, c'est désormais une
liste de ressources analysable.
Le plan annonçait « 25G » quand « qm resize » recevait 20 : la marge
d'ERPLibre se perdait en route, la VM naissait trop petite. « Changer l'état »
choisissait par NOM, or seul le VMID est unique sur un hôte — cocher une VM
en éteignait deux homonymes. L'entrée 13 volait son alias à une VM locale du
même nom. Enfin le déploiement par QUESTIONS avait vieilli seul : il partage
maintenant l'épilogue de l'écran, donc le guide, l'alias protégé, les colonnes
vivantes et le sommaire.
--- EN ---
Three more paths led to 🗑 on a single incident, and "deleted" freezes the row
for good. One failing "virsh list" condemned the WHOLE local fleet. A Proxmox
status outside the three expected ones — prelaunch, suspended, internal-error
— passed for a disappearance. And a remote pipeline's exit code is its LAST
link's: a broken pvesh read as "the host answered without it". Proof of an
answer is now a parsable resource list.
The plan announced "25G" while "qm resize" got 20: ERPLibre's margin was lost
on the way and the VM was born too small. "Change state" selected by NAME,
yet only the VMID is unique on a host — ticking one VM shut down two
namesakes. Menu entry 13 stole its alias from a local VM of the same name.
Finally the QUESTION-driven deployment had aged alone: it now shares the
screen's epilogue — guide, protected alias, live columns and summary.
Assisted-by: Claude Opus 5
2026-08-24 14:15:03 -04:00
|
|
|
|
install = {"branch": branch, "cmd": cmd, "label": label}
|
|
|
|
|
|
spec_finale = {
|
|
|
|
|
|
"host": host,
|
|
|
|
|
|
"storage": stockage,
|
|
|
|
|
|
"bridge": pont,
|
|
|
|
|
|
"res_label": "",
|
|
|
|
|
|
"vms": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"name": nom,
|
|
|
|
|
|
"vmid": vmid,
|
|
|
|
|
|
"distro": distro,
|
|
|
|
|
|
"version": version,
|
|
|
|
|
|
"arch": arch,
|
|
|
|
|
|
"ram": memoire,
|
|
|
|
|
|
"vcpus": vcpus,
|
|
|
|
|
|
"disk": disque,
|
|
|
|
|
|
"desktop": "",
|
|
|
|
|
|
"install_cmd": "",
|
|
|
|
|
|
"ipconfig": ipconfig,
|
|
|
|
|
|
}
|
|
|
|
|
|
],
|
|
|
|
|
|
"existing": [],
|
|
|
|
|
|
"user": "erplibre",
|
|
|
|
|
|
"ssh_key": cle_locale or "",
|
|
|
|
|
|
"add_ssh_config": True,
|
|
|
|
|
|
"install": install,
|
|
|
|
|
|
"monitor": True,
|
|
|
|
|
|
"python_provider": "",
|
[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
|
|
|
|
# La voie par questions ne demande pas le fuseau — l'écran le
|
|
|
|
|
|
# fait. Sans ce défaut, elle laissait la VM en UTC, alors que la
|
|
|
|
|
|
# voie libvirt reprend le fuseau de l'hôte depuis toujours.
|
|
|
|
|
|
"timezone": self._qemu_host_timezone(),
|
[FIX] proxmox : six défauts trouvés par un audit, pas à l'usage
Trois autres chemins menaient au 🗑 sur un seul incident, et « effacée » gèle
la ligne pour de bon. Un « virsh list » en échec condamnait TOUT le parc
local. Un statut Proxmox hors des trois attendus — prelaunch, suspended,
internal-error — passait pour une disparition. Et le code de sortie de la
suite distante est celui de son DERNIER maillon : un pvesh en panne se lisait
« l'hôte a répondu sans elle ». Ce qui prouve une réponse, c'est désormais une
liste de ressources analysable.
Le plan annonçait « 25G » quand « qm resize » recevait 20 : la marge
d'ERPLibre se perdait en route, la VM naissait trop petite. « Changer l'état »
choisissait par NOM, or seul le VMID est unique sur un hôte — cocher une VM
en éteignait deux homonymes. L'entrée 13 volait son alias à une VM locale du
même nom. Enfin le déploiement par QUESTIONS avait vieilli seul : il partage
maintenant l'épilogue de l'écran, donc le guide, l'alias protégé, les colonnes
vivantes et le sommaire.
--- EN ---
Three more paths led to 🗑 on a single incident, and "deleted" freezes the row
for good. One failing "virsh list" condemned the WHOLE local fleet. A Proxmox
status outside the three expected ones — prelaunch, suspended, internal-error
— passed for a disappearance. And a remote pipeline's exit code is its LAST
link's: a broken pvesh read as "the host answered without it". Proof of an
answer is now a parsable resource list.
The plan announced "25G" while "qm resize" got 20: ERPLibre's margin was lost
on the way and the VM was born too small. "Change state" selected by NAME,
yet only the VMID is unique on a host — ticking one VM shut down two
namesakes. Menu entry 13 stole its alias from a local VM of the same name.
Finally the QUESTION-driven deployment had aged alone: it now shares the
screen's epilogue — guide, protected alias, live columns and summary.
Assisted-by: Claude Opus 5
2026-08-24 14:15:03 -04:00
|
|
|
|
}
|
|
|
|
|
|
joignables = self._pve_after_create(
|
|
|
|
|
|
host, spec_finale, [nom], cle_locale
|
|
|
|
|
|
)
|
|
|
|
|
|
self._pve_print_summary(spec_finale, joignables or [], "")
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
|
|
|
|
|
|
def _pve_ssh_config(self):
|
|
|
|
|
|
"""Écrit une entrée ~/.ssh/config par VM de l'hôte, avec l'hôte
|
|
|
|
|
|
Proxmox en ProxyJump — sans quoi ces VM ne sont joignables d'ici que
|
|
|
|
|
|
si leur réseau est routé jusqu'à nous."""
|
|
|
|
|
|
host = self._pve_host()
|
|
|
|
|
|
if not host:
|
|
|
|
|
|
return
|
|
|
|
|
|
vms = [v for v in self._pve_vms() if v["status"] == "running"]
|
|
|
|
|
|
if not vms:
|
|
|
|
|
|
print(f"\n{t('No running VM on this Proxmox host.')}")
|
|
|
|
|
|
return
|
|
|
|
|
|
cle = self._ssh_private_key(self._qemu_default_ssh_key())
|
[FIX] proxmox : six défauts trouvés par un audit, pas à l'usage
Trois autres chemins menaient au 🗑 sur un seul incident, et « effacée » gèle
la ligne pour de bon. Un « virsh list » en échec condamnait TOUT le parc
local. Un statut Proxmox hors des trois attendus — prelaunch, suspended,
internal-error — passait pour une disparition. Et le code de sortie de la
suite distante est celui de son DERNIER maillon : un pvesh en panne se lisait
« l'hôte a répondu sans elle ». Ce qui prouve une réponse, c'est désormais une
liste de ressources analysable.
Le plan annonçait « 25G » quand « qm resize » recevait 20 : la marge
d'ERPLibre se perdait en route, la VM naissait trop petite. « Changer l'état »
choisissait par NOM, or seul le VMID est unique sur un hôte — cocher une VM
en éteignait deux homonymes. L'entrée 13 volait son alias à une VM locale du
même nom. Enfin le déploiement par QUESTIONS avait vieilli seul : il partage
maintenant l'épilogue de l'écran, donc le guide, l'alias protégé, les colonnes
vivantes et le sommaire.
--- EN ---
Three more paths led to 🗑 on a single incident, and "deleted" freezes the row
for good. One failing "virsh list" condemned the WHOLE local fleet. A Proxmox
status outside the three expected ones — prelaunch, suspended, internal-error
— passed for a disappearance. And a remote pipeline's exit code is its LAST
link's: a broken pvesh read as "the host answered without it". Proof of an
answer is now a parsable resource list.
The plan announced "25G" while "qm resize" got 20: ERPLibre's margin was lost
on the way and the VM was born too small. "Change state" selected by NAME,
yet only the VMID is unique on a host — ticking one VM shut down two
namesakes. Menu entry 13 stole its alias from a local VM of the same name.
Finally the QUESTION-driven deployment had aged alone: it now shares the
screen's epilogue — guide, protected alias, live columns and summary.
Assisted-by: Claude Opus 5
2026-08-24 14:15:03 -04:00
|
|
|
|
# Les domaines LOCAUX : un nom partagé avec l'un d'eux ne doit pas lui
|
|
|
|
|
|
# voler son alias — même règle que le déploiement.
|
|
|
|
|
|
locaux = set(self._qemu_list_domains())
|
|
|
|
|
|
hote_court = (host.get("target") or "").split("@")[-1]
|
|
|
|
|
|
hote_court = re.sub(r"[^A-Za-z0-9._-]", "-", hote_court) or "pve"
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
for vm in vms:
|
|
|
|
|
|
ip = self._pve_guest_ip(vm["vmid"], attente=0)
|
|
|
|
|
|
if not ip:
|
|
|
|
|
|
print(f" ⚠ {vm['name']} : {t('no address, skipped')}")
|
|
|
|
|
|
continue
|
[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
|
|
|
|
noms, vole = self._pve_alias_names(
|
|
|
|
|
|
vm["name"],
|
|
|
|
|
|
f"{hote_court}+{vm['name']}",
|
|
|
|
|
|
locaux,
|
|
|
|
|
|
host["target"],
|
|
|
|
|
|
)
|
|
|
|
|
|
if vole:
|
[FIX] proxmox : six défauts trouvés par un audit, pas à l'usage
Trois autres chemins menaient au 🗑 sur un seul incident, et « effacée » gèle
la ligne pour de bon. Un « virsh list » en échec condamnait TOUT le parc
local. Un statut Proxmox hors des trois attendus — prelaunch, suspended,
internal-error — passait pour une disparition. Et le code de sortie de la
suite distante est celui de son DERNIER maillon : un pvesh en panne se lisait
« l'hôte a répondu sans elle ». Ce qui prouve une réponse, c'est désormais une
liste de ressources analysable.
Le plan annonçait « 25G » quand « qm resize » recevait 20 : la marge
d'ERPLibre se perdait en route, la VM naissait trop petite. « Changer l'état »
choisissait par NOM, or seul le VMID est unique sur un hôte — cocher une VM
en éteignait deux homonymes. L'entrée 13 volait son alias à une VM locale du
même nom. Enfin le déploiement par QUESTIONS avait vieilli seul : il partage
maintenant l'épilogue de l'écran, donc le guide, l'alias protégé, les colonnes
vivantes et le sommaire.
--- EN ---
Three more paths led to 🗑 on a single incident, and "deleted" freezes the row
for good. One failing "virsh list" condemned the WHOLE local fleet. A Proxmox
status outside the three expected ones — prelaunch, suspended, internal-error
— passed for a disappearance. And a remote pipeline's exit code is its LAST
link's: a broken pvesh read as "the host answered without it". Proof of an
answer is now a parsable resource list.
The plan announced "25G" while "qm resize" got 20: ERPLibre's margin was lost
on the way and the VM was born too small. "Change state" selected by NAME,
yet only the VMID is unique on a host — ticking one VM shut down two
namesakes. Menu entry 13 stole its alias from a local VM of the same name.
Finally the QUESTION-driven deployment had aged alone: it now shares the
screen's epilogue — guide, protected alias, live columns and summary.
Assisted-by: Claude Opus 5
2026-08-24 14:15:03 -04:00
|
|
|
|
print(
|
[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
|
|
|
|
f" ⚠ {t('This name is already taken by')} {vole} :"
|
|
|
|
|
|
f" {t('the alias goes to')} {noms[0]}"
|
[FIX] proxmox : six défauts trouvés par un audit, pas à l'usage
Trois autres chemins menaient au 🗑 sur un seul incident, et « effacée » gèle
la ligne pour de bon. Un « virsh list » en échec condamnait TOUT le parc
local. Un statut Proxmox hors des trois attendus — prelaunch, suspended,
internal-error — passait pour une disparition. Et le code de sortie de la
suite distante est celui de son DERNIER maillon : un pvesh en panne se lisait
« l'hôte a répondu sans elle ». Ce qui prouve une réponse, c'est désormais une
liste de ressources analysable.
Le plan annonçait « 25G » quand « qm resize » recevait 20 : la marge
d'ERPLibre se perdait en route, la VM naissait trop petite. « Changer l'état »
choisissait par NOM, or seul le VMID est unique sur un hôte — cocher une VM
en éteignait deux homonymes. L'entrée 13 volait son alias à une VM locale du
même nom. Enfin le déploiement par QUESTIONS avait vieilli seul : il partage
maintenant l'épilogue de l'écran, donc le guide, l'alias protégé, les colonnes
vivantes et le sommaire.
--- EN ---
Three more paths led to 🗑 on a single incident, and "deleted" freezes the row
for good. One failing "virsh list" condemned the WHOLE local fleet. A Proxmox
status outside the three expected ones — prelaunch, suspended, internal-error
— passed for a disappearance. And a remote pipeline's exit code is its LAST
link's: a broken pvesh read as "the host answered without it". Proof of an
answer is now a parsable resource list.
The plan announced "25G" while "qm resize" got 20: ERPLibre's margin was lost
on the way and the VM was born too small. "Change state" selected by NAME,
yet only the VMID is unique on a host — ticking one VM shut down two
namesakes. Menu entry 13 stole its alias from a local VM of the same name.
Finally the QUESTION-driven deployment had aged alone: it now shares the
screen's epilogue — guide, protected alias, live columns and summary.
Assisted-by: Claude Opus 5
2026-08-24 14:15:03 -04:00
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
self._write_ssh_config_entry(
|
[FIX] proxmox : six défauts trouvés par un audit, pas à l'usage
Trois autres chemins menaient au 🗑 sur un seul incident, et « effacée » gèle
la ligne pour de bon. Un « virsh list » en échec condamnait TOUT le parc
local. Un statut Proxmox hors des trois attendus — prelaunch, suspended,
internal-error — passait pour une disparition. Et le code de sortie de la
suite distante est celui de son DERNIER maillon : un pvesh en panne se lisait
« l'hôte a répondu sans elle ». Ce qui prouve une réponse, c'est désormais une
liste de ressources analysable.
Le plan annonçait « 25G » quand « qm resize » recevait 20 : la marge
d'ERPLibre se perdait en route, la VM naissait trop petite. « Changer l'état »
choisissait par NOM, or seul le VMID est unique sur un hôte — cocher une VM
en éteignait deux homonymes. L'entrée 13 volait son alias à une VM locale du
même nom. Enfin le déploiement par QUESTIONS avait vieilli seul : il partage
maintenant l'épilogue de l'écran, donc le guide, l'alias protégé, les colonnes
vivantes et le sommaire.
--- EN ---
Three more paths led to 🗑 on a single incident, and "deleted" freezes the row
for good. One failing "virsh list" condemned the WHOLE local fleet. A Proxmox
status outside the three expected ones — prelaunch, suspended, internal-error
— passed for a disappearance. And a remote pipeline's exit code is its LAST
link's: a broken pvesh read as "the host answered without it". Proof of an
answer is now a parsable resource list.
The plan announced "25G" while "qm resize" got 20: ERPLibre's margin was lost
on the way and the VM was born too small. "Change state" selected by NAME,
yet only the VMID is unique on a host — ticking one VM shut down two
namesakes. Menu entry 13 stole its alias from a local VM of the same name.
Finally the QUESTION-driven deployment had aged alone: it now shares the
screen's epilogue — guide, protected alias, live columns and summary.
Assisted-by: Claude Opus 5
2026-08-24 14:15:03 -04:00
|
|
|
|
noms,
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
"erplibre",
|
|
|
|
|
|
ip,
|
|
|
|
|
|
identity_file=cle,
|
|
|
|
|
|
proxy_jump=host["target"],
|
[FIX] proxmox : l'ancienne entrée ssh s'en va avec la convention
Le nom chaîné devient systématique, mais les entrées écrites AVANT portent le
nom court — et rien ne les retirerait : elles ne déclarent pas le nom qu'on
écrit maintenant. Deux blocs mèneraient à la même machine, exactement ce
qu'on venait d'enlever.
Le ProxyJump tranche : un bloc qui rebondit par CET hôte est le nôtre, on le
retire. Celui d'une VM locale homonyme n'en a pas, et on n'y touche jamais ;
celui d'un autre hôte Proxmox non plus.
Le drapeau Odoo gagne son test au passage. Il tombait pour la même raison que
les colonnes vides — la sonde est le dernier maillon de la suite distante, et
un parc où une seule VM n'a pas d'Odoo, un hyperviseur imbriqué par exemple,
finit en échec. Vérifié sur les trois VM : l'hôte rend bien « ODOO » pour les
deux qui écoutent, et le navigateur répondait 303 pendant que la colonne
disait « — ».
--- EN ---
The chained name becomes systematic, but entries written BEFORE carry the
short one — and nothing would retire them: they do not declare the name we
now write. Two blocks would lead to the same machine, exactly what we had
just removed.
The ProxyJump decides: a block hopping through THIS host is ours, so it goes.
A local namesake's has none, and is never touched; another Proxmox host's
neither.
The Odoo flag gains its test along the way. It failed for the same reason as
the empty columns — the probe is the remote pipeline's last link, and a fleet
where a single VM has no Odoo, a nested hypervisor for instance, ends in
failure. Verified on all three VMs: the host does return "ODOO" for the two
that listen, and the browser answered 303 while the column said "—".
Assisted-by: Claude Opus 5
2026-08-25 00:43:10 -04:00
|
|
|
|
also_drop=self._pve_alias_perime(vm["name"], host["target"]),
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
)
|
[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
|
|
|
|
print(
|
[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
|
|
|
|
f" ✓ ssh {noms[0]} ({ip} {t('through')} {host['target']})"
|
[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
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
|
|
|
|
|
|
def _pve_test_vm(self):
|
|
|
|
|
|
"""Ouvre Odoo (:8069) d'une VM Proxmox dans un navigateur en ligne.
|
|
|
|
|
|
|
|
|
|
|
|
Même chose que pour QEMU, à ceci près que l'adresse vient de l'hôte
|
|
|
|
|
|
Proxmox et non de libvirt — et qu'elle n'est joignable d'ici que si son
|
|
|
|
|
|
réseau l'est. On le dit plutôt que d'ouvrir une page vide.
|
|
|
|
|
|
"""
|
|
|
|
|
|
vm = self._pve_pick_vm()
|
|
|
|
|
|
if not vm:
|
|
|
|
|
|
return
|
|
|
|
|
|
ip = self._pve_guest_ip(vm["vmid"], attente=30)
|
|
|
|
|
|
if not ip:
|
|
|
|
|
|
print(f"\n ⚠ {t('No address for this VM.')}")
|
|
|
|
|
|
return
|
|
|
|
|
|
if not self._qemu_ip_reachable(ip, port=8069, timeout=3):
|
|
|
|
|
|
print(f"\n ⚠ {ip}:8069 {t('unreachable from here.')}")
|
[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
|
|
|
|
print(
|
|
|
|
|
|
f" → {t('Use [13] to add a ProxyJump entry, then a tunnel.')}"
|
|
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
return
|
|
|
|
|
|
navigateur = self._qemu_choose_cli_browser()
|
|
|
|
|
|
if not navigateur:
|
|
|
|
|
|
return
|
|
|
|
|
|
url = f"http://{ip}:8069"
|
|
|
|
|
|
print(f"→ {navigateur} {url}")
|
|
|
|
|
|
os.system(f"{navigateur} {shlex.quote(url)}")
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_example(self):
|
|
|
|
|
|
"""Exemple de séquence, sans rien exécuter : de quoi voir ce que
|
|
|
|
|
|
l'outil enverrait sur l'hôte."""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
spec = {
|
|
|
|
|
|
"name": "demo-vm",
|
|
|
|
|
|
"memory": 4096,
|
|
|
|
|
|
"vcpus": 2,
|
|
|
|
|
|
"disk": "32G",
|
|
|
|
|
|
"storage": "local-lvm",
|
|
|
|
|
|
"bridge": "vmbr0",
|
|
|
|
|
|
"image": "debian-13-genericcloud-amd64.qcow2",
|
|
|
|
|
|
"sshkey_path": "/root/.ssh/erplibre-deploy.pub",
|
|
|
|
|
|
}
|
|
|
|
|
|
print(f"\n── {t('Example: demo-vm, Debian 13, on a Proxmox 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
|
|
|
|
print(
|
|
|
|
|
|
f" {pve.image_fetch_cmd('https://…/debian-13.qcow2', spec['image'])}"
|
|
|
|
|
|
)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
for cmd in pve.create_cmds(101, spec):
|
|
|
|
|
|
print(f" {cmd}")
|
|
|
|
|
|
|
|
|
|
|
|
def _pve_stats(self):
|
|
|
|
|
|
"""État de l'hôte et de ses VM, en une page."""
|
|
|
|
|
|
host = self._pve_host()
|
|
|
|
|
|
if not host:
|
|
|
|
|
|
return
|
|
|
|
|
|
print(f"\n══ {t('Proxmox host:')} {self._pve_label(host)} ══")
|
|
|
|
|
|
for titre, cmd in (
|
|
|
|
|
|
(t("uptime"), "uptime"),
|
|
|
|
|
|
(t("memory"), "free -h | head -2"),
|
|
|
|
|
|
(t("storages"), "pvesm status"),
|
|
|
|
|
|
):
|
|
|
|
|
|
code, out = self._pve_show(cmd, quiet=True)
|
|
|
|
|
|
print(f"\n── {titre} ──")
|
|
|
|
|
|
print((out or "").rstrip() if code == 0 else f" ⚠ {out.strip()}")
|
|
|
|
|
|
self._pve_list()
|
|
|
|
|
|
|
|
|
|
|
|
def prompt_execute_proxmox(self):
|
|
|
|
|
|
"""Sous-menu Proxmox VE : l'équivalent du menu QEMU/KVM, mais sur un
|
|
|
|
|
|
hôte DISTANT. La première question est donc « lequel ? » — et la
|
|
|
|
|
|
réponse est retenue pour toute la session."""
|
|
|
|
|
|
print(f"🤖 {t('Deploy a virtual machine on Proxmox VE!')}")
|
|
|
|
|
|
if not self._pve_host():
|
|
|
|
|
|
return False
|
|
|
|
|
|
choices = [
|
|
|
|
|
|
{"section": t("Deployment")},
|
|
|
|
|
|
{"prompt_description": t("Deploy a VM on the Proxmox host")},
|
|
|
|
|
|
{
|
|
|
|
|
|
"prompt_description": t(
|
|
|
|
|
|
"Preview a deployment (dry-run, nothing sent)"
|
|
|
|
|
|
)
|
|
|
|
|
|
},
|
|
|
|
|
|
{"prompt_description": t("Download a cloud image on the host")},
|
|
|
|
|
|
{
|
|
|
|
|
|
"prompt_description": t(
|
|
|
|
|
|
"Reopen install monitoring (last run / history)"
|
|
|
|
|
|
)
|
|
|
|
|
|
},
|
|
|
|
|
|
{"section": t("Manage")},
|
|
|
|
|
|
{"prompt_description": t("List VMs (qm list)")},
|
|
|
|
|
|
{"prompt_description": t("Show a VM IP address")},
|
|
|
|
|
|
{"prompt_description": t("Open the console on a VM")},
|
|
|
|
|
|
{"prompt_description": t("Resize a VM disk")},
|
|
|
|
|
|
{"prompt_description": t("Delete VM(s)")},
|
|
|
|
|
|
{"prompt_description": t("Clean up (orphan disks)")},
|
|
|
|
|
|
{
|
|
|
|
|
|
"prompt_description": t(
|
|
|
|
|
|
"Test a VM (open Odoo in a CLI browser)"
|
|
|
|
|
|
)
|
|
|
|
|
|
},
|
|
|
|
|
|
{"prompt_description": t("Statistics (host and VMs)")},
|
|
|
|
|
|
{
|
|
|
|
|
|
"prompt_description": t(
|
|
|
|
|
|
"SSH configuration (~/.ssh/config, ProxyJump)"
|
|
|
|
|
|
)
|
|
|
|
|
|
},
|
[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
|
|
|
|
{
|
|
|
|
|
|
"prompt_description": t(
|
|
|
|
|
|
"Remote desktop tunnel (VNC/RDP over SSH)"
|
|
|
|
|
|
)
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"prompt_description": t(
|
|
|
|
|
|
"Android emulator (start, tunnel, scrcpy)"
|
|
|
|
|
|
)
|
|
|
|
|
|
},
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
{"section": t("Catalog")},
|
|
|
|
|
|
{"prompt_description": t("List available images and their specs")},
|
|
|
|
|
|
{"prompt_description": t("Proxmox - example sequence (dry-run)")},
|
|
|
|
|
|
{"section": t("Host")},
|
|
|
|
|
|
{"prompt_description": t("Change the Proxmox host")},
|
|
|
|
|
|
]
|
[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
|
|
|
|
# Même extension que le menu QEMU/KVM : ce que todo.json ajoute
|
|
|
|
|
|
# s'affiche à la suite et se lance par son numéro.
|
|
|
|
|
|
supplement = self.config_file.get_config("proxmox_from_makefile")
|
|
|
|
|
|
if supplement:
|
|
|
|
|
|
choices.extend(supplement)
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
help_info = self.fill_help_info(choices)
|
|
|
|
|
|
while True:
|
|
|
|
|
|
hote = self._pve_host(ask=False)
|
|
|
|
|
|
print(f"\n {t('Proxmox host:')} {self._pve_label(hote) or '-'}")
|
|
|
|
|
|
status = click.prompt(help_info)
|
|
|
|
|
|
print()
|
|
|
|
|
|
if status == "0":
|
|
|
|
|
|
return False
|
|
|
|
|
|
elif status == "1":
|
|
|
|
|
|
self._pve_deploy()
|
|
|
|
|
|
elif status == "2":
|
|
|
|
|
|
self._pve_deploy(dry_run=True)
|
|
|
|
|
|
elif status == "3":
|
|
|
|
|
|
self._pve_fetch_image()
|
|
|
|
|
|
elif status == "4":
|
|
|
|
|
|
self._qemu_reopen_monitor()
|
|
|
|
|
|
elif status == "5":
|
|
|
|
|
|
self._pve_list()
|
|
|
|
|
|
elif status == "6":
|
|
|
|
|
|
self._pve_vm_ip()
|
|
|
|
|
|
elif status == "7":
|
|
|
|
|
|
self._pve_console()
|
|
|
|
|
|
elif status == "8":
|
|
|
|
|
|
self._pve_resize()
|
|
|
|
|
|
elif status == "9":
|
|
|
|
|
|
self._pve_delete()
|
|
|
|
|
|
elif status == "10":
|
|
|
|
|
|
self._pve_cleanup()
|
|
|
|
|
|
elif status == "11":
|
|
|
|
|
|
self._pve_test_vm()
|
|
|
|
|
|
elif status == "12":
|
|
|
|
|
|
self._pve_stats()
|
|
|
|
|
|
elif status == "13":
|
|
|
|
|
|
self._pve_ssh_config()
|
|
|
|
|
|
elif status == "14":
|
|
|
|
|
|
# Les VM Proxmox sont dans ~/.ssh/config (entrée 13) : le
|
|
|
|
|
|
# tunnel du menu QEMU les y trouve, rebond compris.
|
|
|
|
|
|
self._qemu_tunnel_menu()
|
|
|
|
|
|
elif status == "15":
|
|
|
|
|
|
self._qemu_emulator_menu()
|
|
|
|
|
|
elif status == "16":
|
|
|
|
|
|
self._qemu_list_images()
|
|
|
|
|
|
elif status == "17":
|
|
|
|
|
|
self._pve_example()
|
|
|
|
|
|
elif status == "18":
|
|
|
|
|
|
self._pve_forget_host()
|
|
|
|
|
|
self._pve_pick_host()
|
|
|
|
|
|
else:
|
[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
|
|
|
|
introuvable = True
|
|
|
|
|
|
try:
|
|
|
|
|
|
numero = int(status)
|
|
|
|
|
|
# Les sections ne comptent pas dans la numérotation.
|
|
|
|
|
|
reelles = [c for c in choices if not c.get("section")]
|
|
|
|
|
|
if 0 < numero <= len(reelles):
|
|
|
|
|
|
introuvable = False
|
|
|
|
|
|
self.execute_from_configuration(reelles[numero - 1])
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
if introuvable:
|
|
|
|
|
|
print(t("Command not found !"))
|
[REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.
Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.
--- EN ---
todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.
Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.
Assisted-by: Claude Opus 5
2026-08-23 18:01:09 -04:00
|
|
|
|
|
|
|
|
|
|
def _pve_fetch_image(self):
|
|
|
|
|
|
"""Télécharge une image cloud SUR l'hôte Proxmox.
|
|
|
|
|
|
|
|
|
|
|
|
Là et pas ici : c'est sur l'hôte que le disque sera écrit, et faire
|
|
|
|
|
|
descendre 325 Mio chez soi pour les renvoyer doublerait le transfert.
|
|
|
|
|
|
"""
|
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve
|
|
|
|
|
|
|
|
|
|
|
|
mod = self._qemu_import_module()
|
|
|
|
|
|
distro = self._qemu_prompt_distro()
|
|
|
|
|
|
version = self._qemu_prompt_version(distro)
|
|
|
|
|
|
code = mod.DISTROS[distro][0][version][0]
|
|
|
|
|
|
url = mod.image_url(distro, code, "amd64", version)
|
|
|
|
|
|
nom = mod.default_image_name(distro, code, "amd64", version)
|
|
|
|
|
|
print(f"\n {nom}\n {url}")
|
2026-09-16 00:42:31 -04:00
|
|
|
|
# La somme, ici comme au déploiement : une image téléchargée d'avance
|
|
|
|
|
|
# est celle qu'un déploiement futur trouvera « déjà présente », et il
|
|
|
|
|
|
# ne la regardera pas mieux que celui-ci.
|
|
|
|
|
|
self._pve_show(
|
|
|
|
|
|
pve.image_fetch_cmd(url, nom, sha256=mod.pinned_sha256(distro)),
|
|
|
|
|
|
timeout=1800,
|
|
|
|
|
|
)
|