erplibre/script/todo/proxmox_deploy_form.py

924 lines
39 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
"""Formulaire de déploiement sur un hôte Proxmox VE.
Même écran que pour QEMU/KVM — catalogue à gauche, plan à droite, totaux
dessous — parce que c'est le même travail : choisir des systèmes, régler des
ressources, vérifier avant de lancer. Tout ce qui est commun vient de
`deploy_form_lib` (logique pure, socle CSS, fabrique des ressources) et de
`deploy_form_plan` (surcharges, verrous, exemplaires, renommage). Ne reste
ici que ce que Proxmox a en propre :
* l'hôte, choisi AVANT d'ouvrir l'écran — il faut ssh et sudo, et une invite
de mot de passe pendant que Textual affiche casserait le terminal ;
* le stockage et le pont, LUS SUR L'HÔTE : « local-lvm » n'existe pas partout
et un pont inventé fait échouer « qm create » ;
* le VMID, et l'adresse qui s'en déduit sur un pont interne.
Le formulaire ne touche à rien : il rend une spec. C'est l'appelant
(`ProxmoxMenuMixin._pve_deploy`) qui exécute.
"""
import os
import re
from script.todo.deploy_form_lib import (
CSS_BASE,
FREE,
RES_FIELDS,
SELECT_TO_FIELD,
[FIX] deploy : la branche du dépôt, et la sortie pendant qu'elle arrive Trois défauts rapportés sur un déploiement Proxmox. La branche proposée était « dependabot/pip/aiobotocore-3.1.3 » : « git ls-remote » rend la liste par ordre alphabétique, et l'écran en prenait la première. Il propose maintenant la branche du DÉPÔT — celle qu'on a sous les yeux — puis develop, puis master ; la liste met ces trois-là en tête et relègue les branches de robot à la fin. Les deux écrans partagent la règle. La vue de progression n'affichait la sortie qu'à la FIN du travail : « subprocess.run » ne rend rien avant. Sur Proxmox, une VM demande le téléchargement de 325 Mio puis l'import du disque — plusieurs minutes de bloc vide. Elle lit maintenant ligne à ligne. Et « s » y ouvre un ssh sur la VM créée, par son nom, donc par l'entrée ~/.ssh/config qui porte le rebond. --- EN --- Three defects reported on a Proxmox deployment. The branch offered was "dependabot/pip/aiobotocore-3.1.3": "git ls-remote" returns the list in alphabetical order and the screen took its first entry. It now offers the CHECKOUT's branch — the one in front of you — then develop, then master; the list puts those three first and pushes robot branches to the end. Both screens share the rule. The progress view only showed output at the END of a job: "subprocess.run" returns nothing before that. On Proxmox a VM needs a 325 MiB download then a disk import — minutes of an empty block. It now reads line by line. And "s" opens an ssh to the created VM, by name, hence through the ~/.ssh/config entry that carries the jump. Assisted-by: Claude Opus 5
2026-08-24 05:38:28 -04:00
branch_default,
branch_order,
build_vms,
disk_note,
entry_key,
gib,
plan_rows,
plan_totals,
res_row_widgets,
t,
)
[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 ExtrasMixin
from script.todo.deploy_form_plan import PlanMixin, preview_screen
# Aucun disque orphelin à craindre : les disques d'un Proxmox distant vivent
# dans un stockage que seul l'hôte connaît, jamais dans /var/lib/libvirt.
PAS_D_ORPHELIN = None
[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
# Dernier choix du sélecteur de pont : il ne désigne pas un pont, il en crée
# un. Rapporté — l'écran refusait de déployer « aucun pont sur l'hôte » sans
# offrir le moindre moyen d'en avoir un.
CREER_PONT = "__creer_pont__"
def assign_vmids(rows, used, start, ipconfig):
"""Pose un VMID libre et son adresse sur chaque VM À CRÉER.
Proxmox refuse un VMID déjà pris, et il le dit APRÈS le téléchargement de
l'image : on choisit donc avant, d'après ce que l'hôte déclare. Les VM qui
existent déjà sont sautées — elles ont le leur.
`ipconfig(vmid)` rend la ligne cloud-init : « ip=dhcp » sur un pont qui
donne sur le LAN, une adresse fixe dérivée du VMID sur un pont interne.
"""
pris = {int(v) for v in used or () if str(v).isdigit()}
suivant = max(int(start or 0), 100)
for r in rows:
if r["state"] == "exists":
continue
while suivant in pris:
suivant += 1
pris.add(suivant)
r["vm"]["vmid"] = suivant
r["vm"]["ipconfig"] = ipconfig(suivant) if ipconfig else "ip=dhcp"
suivant += 1
return rows
def res_label(profile) -> str:
"""Comment le plan nomme le réglage commun choisi."""
return t("custom") if profile == "custom" else f"x{profile}"
def build_spec(vms, existants, form):
"""La spec que le déploiement exécutera. Une VM qui existe déjà n'y entre
pas : Proxmox refuserait le VMID, et on ne veut surtout pas l'écraser."""
connus = set(existants)
return {
"host": form["host"],
"storage": form["storage"],
"bridge": form["bridge"],
[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
# Les résolveurs de l'hôte suivent la spec : une VM en adresse fixe
# n'a pas de DNS sans eux.
"nameservers": form.get("nameservers") or (),
"res_label": form["res_label"],
"vms": [vm for vm in vms if vm["name"] not in connus],
"existing": [vm["name"] for vm in vms if vm["name"] in connus],
"ssh_key": form["ssh_key"],
"user": form.get("user") or "erplibre",
"start": form["start"],
"add_ssh_config": form["add_ssh_config"],
"install": form["install"],
[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": form.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
# Ce qui décrit le SYSTÈME INVITÉ, et non l'hyperviseur : il valait
# déjà sur libvirt, il vaut ici. Sans ces cinq clés, une VM créée sur
# Proxmox naissait sans bureau, sans outils et en UTC.
"timezone": form.get("timezone") or "",
"desktop": form.get("desktop") or "",
"vm_tools": tuple(form.get("vm_tools") or ()),
"app_store": form.get("app_store") or "deb",
"prod": bool((form.get("install") or {}).get("prod")),
"monitor": form["monitor"],
"parallelism": form["parallelism"],
}
def run_proxmox_form(ctx, run_app: bool = True):
"""Formulaire Proxmox. Renvoie une spec, None si annulé, {} pour retomber
sur les invites textuelles. `run_app=False` rend l'instance sans la lancer
(tests headless)."""
from textual.app import App, ComposeResult
2026-09-04 02:03:52 -04:00
from textual.binding import Binding
from textual.containers import Horizontal, Vertical, VerticalScroll
from textual.widgets import (
Button,
Checkbox,
Footer,
Header,
Input,
RadioButton,
RadioSet,
Select,
SelectionList,
Static,
)
SELECT_NULL = getattr(Select, "NULL", Select.BLANK)
arches = ctx["arches"]
catalog = ctx["catalog"]
noms_pris = ctx["names"]
vmids_pris = ctx["vmids"]
[FIX] deploy : la branche du dépôt, et la sortie pendant qu'elle arrive Trois défauts rapportés sur un déploiement Proxmox. La branche proposée était « dependabot/pip/aiobotocore-3.1.3 » : « git ls-remote » rend la liste par ordre alphabétique, et l'écran en prenait la première. Il propose maintenant la branche du DÉPÔT — celle qu'on a sous les yeux — puis develop, puis master ; la liste met ces trois-là en tête et relègue les branches de robot à la fin. Les deux écrans partagent la règle. La vue de progression n'affichait la sortie qu'à la FIN du travail : « subprocess.run » ne rend rien avant. Sur Proxmox, une VM demande le téléchargement de 325 Mio puis l'import du disque — plusieurs minutes de bloc vide. Elle lit maintenant ligne à ligne. Et « s » y ouvre un ssh sur la VM créée, par son nom, donc par l'entrée ~/.ssh/config qui porte le rebond. --- EN --- Three defects reported on a Proxmox deployment. The branch offered was "dependabot/pip/aiobotocore-3.1.3": "git ls-remote" returns the list in alphabetical order and the screen took its first entry. It now offers the CHECKOUT's branch — the one in front of you — then develop, then master; the list puts those three first and pushes robot branches to the end. Both screens share the rule. The progress view only showed output at the END of a job: "subprocess.run" returns nothing before that. On Proxmox a VM needs a 325 MiB download then a disk import — minutes of an empty block. It now reads line by line. And "s" opens an ssh to the created VM, by name, hence through the ~/.ssh/config entry that carries the jump. Assisted-by: Claude Opus 5
2026-08-24 05:38:28 -04:00
# Ordre et défaut partagés avec le formulaire QEMU/KVM : la liste vient
# de « git ls-remote », alphabétique, et commençait donc par une branche
# de dependabot — proposée par défaut. Rapporté.
branches = branch_order(
ctx.get("branches") or ["master"], ctx.get("branch_current")
)
profiles = ctx.get("install_profiles") 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
# {clé de saveur: suffixe de nom} — décrit par todo.py, source unique.
desktop_suffixes = dict(ctx.get("desktop_suffixes") or {})
stockages = ctx.get("storages") or []
ponts = ctx.get("bridges") or []
# {système: (libellé, commande)} — ce qu'un système impose d'installer.
distro_profiles = ctx.get("distro_profiles") or {}
# Les commandes qui ne posent PAS ERPLibre : sa marge disque ne les suit
# pas. DÉDUITES des profils imposés — une seconde clé de contexte à tenir
# en accord avec la première aurait fini par en différer, et la marge
# serait revenue sans qu'on le voie. Jugé sur la commande effective de la
# rangée : un choix explicite compte donc autant que la règle du système.
no_erplibre = {
impose[1].strip() for impose in distro_profiles.values() if impose
}
def entry_label(e):
[FIX] proxmox : quatre écrans qui parlaient d'une machine locale La confirmation de suppression promettait à TOUTE VM « son disque qcow2 EFFACÉ », puis nommait /var/lib/libvirt/images/<nom>.qcow2. Sur une VM Proxmox ce fichier n'existe pas — au mieux, au pire c'est celui d'une autre VM du même nom. C'est la peur exacte qui avait fait remonter le nettoyage. Elle nomme désormais l'hôte, le VMID et « qm destroy ». « Console de l'hyperviseur » lisait le port par « virsh vncdisplay ». Un Proxmox n'a pas de libvirt : l'échec se lisait « écran fermé » et on conseillait « sudo virsh edit » sur une machine sans ce binaire. Ce n'est pas un écran fermé, c'est la mauvaise question — Proxmox sert le sien par un ticket. Les deux vrais chemins sont nommés : la console série, l'interface web par tunnel. La colonne Odoo était un 🟢 acquis pour toujours : « Odoo ne redescend pas en cours d'install » est faux — le service redémarre au moins une fois, et il lui arrive de mourir. Elle est relue, gratuitement sur Proxmox, toutes les trente secondes ailleurs. Un hôte muet reste distinct d'un Odoo tombé. Enfin « Versions principales » (F7) manquait à l'écran Proxmox, qui affiche pourtant le même catalogue. Les trois gestes du catalogue vivent maintenant dans le socle du plan, où ils ne peuvent plus diverger. --- EN --- The delete confirmation promised EVERY VM "its qcow2 disk ERASED", then named /var/lib/libvirt/images/<name>.qcow2. On a Proxmox VM that file does not exist — at best; at worst it is another VM's, of the same name. That is the very fear that surfaced the cleanup report. It now names the host, the VMID and "qm destroy". "Hypervisor console" read the port through "virsh vncdisplay". Proxmox has no libvirt: the failure read as "screen closed" and we advised "sudo virsh edit" on a machine without that binary. It is not a closed screen, it is the wrong question — Proxmox serves its own by ticket. Both real paths are named: the serial console, the web interface through a tunnel. The Odoo column was a 🟢 acquired forever: "Odoo does not go back down during the install" is false — the service restarts at least once, and it does die. It is re-read, free on Proxmox, every thirty seconds elsewhere. A silent host stays distinct from a dead Odoo. Finally "Main versions" (F7) was missing from the Proxmox screen, which shows the same catalog. The catalog's three gestures now live in the plan foundation, where they can no longer diverge. Assisted-by: Claude Opus 5
2026-08-24 23:07:55 -04:00
# L'étoile marque la version par défaut d'une distribution : c'est
# elle que « F7 » retient, et sans repère le raccourci choisissait
# sans qu'on sache quoi.
star = " *" if e.get("default") else ""
return (
f"{e['distro']} {e['version']}{star} [{e['arch']}] {e['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
class ProxmoxForm(ExtrasMixin, PlanMixin, App):
TITLE = t("Deploy one or more ERPLibre VMs on Proxmox VE!")
BINDINGS = [
("f5", "deploy", t("Deploy")),
("f4", "clear_vm", t("Reset VM")),
("f3", "preview", t("Preview")),
("f6", "select_all", t("All")),
[FIX] proxmox : quatre écrans qui parlaient d'une machine locale La confirmation de suppression promettait à TOUTE VM « son disque qcow2 EFFACÉ », puis nommait /var/lib/libvirt/images/<nom>.qcow2. Sur une VM Proxmox ce fichier n'existe pas — au mieux, au pire c'est celui d'une autre VM du même nom. C'est la peur exacte qui avait fait remonter le nettoyage. Elle nomme désormais l'hôte, le VMID et « qm destroy ». « Console de l'hyperviseur » lisait le port par « virsh vncdisplay ». Un Proxmox n'a pas de libvirt : l'échec se lisait « écran fermé » et on conseillait « sudo virsh edit » sur une machine sans ce binaire. Ce n'est pas un écran fermé, c'est la mauvaise question — Proxmox sert le sien par un ticket. Les deux vrais chemins sont nommés : la console série, l'interface web par tunnel. La colonne Odoo était un 🟢 acquis pour toujours : « Odoo ne redescend pas en cours d'install » est faux — le service redémarre au moins une fois, et il lui arrive de mourir. Elle est relue, gratuitement sur Proxmox, toutes les trente secondes ailleurs. Un hôte muet reste distinct d'un Odoo tombé. Enfin « Versions principales » (F7) manquait à l'écran Proxmox, qui affiche pourtant le même catalogue. Les trois gestes du catalogue vivent maintenant dans le socle du plan, où ils ne peuvent plus diverger. --- EN --- The delete confirmation promised EVERY VM "its qcow2 disk ERASED", then named /var/lib/libvirt/images/<name>.qcow2. On a Proxmox VM that file does not exist — at best; at worst it is another VM's, of the same name. That is the very fear that surfaced the cleanup report. It now names the host, the VMID and "qm destroy". "Hypervisor console" read the port through "virsh vncdisplay". Proxmox has no libvirt: the failure read as "screen closed" and we advised "sudo virsh edit" on a machine without that binary. It is not a closed screen, it is the wrong question — Proxmox serves its own by ticket. Both real paths are named: the serial console, the web interface through a tunnel. The Odoo column was a 🟢 acquired forever: "Odoo does not go back down during the install" is false — the service restarts at least once, and it does die. It is re-read, free on Proxmox, every thirty seconds elsewhere. A silent host stays distinct from a dead Odoo. Finally "Main versions" (F7) was missing from the Proxmox screen, which shows the same catalog. The catalog's three gestures now live in the plan foundation, where they can no longer diverge. Assisted-by: Claude Opus 5
2026-08-24 23:07:55 -04:00
("f7", "select_main", t("Main versions")),
("f8", "select_none", t("None")),
2026-09-04 02:03:52 -04:00
("f1", "help", t("Help")),
# « ? » aussi, parce que c'est la touche qu'on essaie d'abord.
# Cachée du pied de page : la même action deux fois s'y lirait
# comme deux aides. Un champ de saisie qui a le focus l'avale, et
# c'est pourquoi F1 existe à côté.
Binding("?", "help", t("Help"), show=False),
("escape", "cancel", t("Cancel")),
]
# Le socle porte la mise en page et les modales ; ne reste ici que ce
# qui nomme les widgets propres à Proxmox.
CSS = (
CSS_BASE
+ """
SelectionList { height: 10; border: solid $panel; }
RadioSet { height: auto; layout: horizontal; }
[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
/* Ces deux règles portent « .vmrow Select » EN PLUS de leur
classe : « .vmrow Select » (une classe + un type) l'emporte sur
« .vmbranch » (une classe) par spécificité CSS. Écrites simplement,
elles étaient silencieusement écrasées. La branche porte des noms
longs (« 1.6.0 », « develop », « feature/xyz ») : trop étroite, la
liste les tronque et on ne sait plus ce qu'on a choisi. */
.vmrow Select.vmbranch { width: 34; }
[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
.vmrow Select.vmprof { width: 40; }
#hostline { height: 1; color: $accent; padding: 0 1; }
"""
)
def __init__(self):
super().__init__()
self.arch = ctx.get("native") or arches[0]
self.profile = "1"
self.custom = {}
self.overrides = {}
self.locked = set()
self.copies = {}
self.rows = []
self.vms = []
self.result = None
# Génération des widgets de rangée : un message qui arrive d'un
# jeu périmé ne doit pas être pris pour une saisie.
self._gen = 0
self._shown_ids = ()
self._syncing = False
[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
# La liste des ponts GRANDIT : l'écran sait en créer un.
self._ponts = list(ponts)
[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
self.extras_init(ctx, branches, profiles)
# ---------------------------------------------------------------- #
# L'écran
# ---------------------------------------------------------------- #
def compose(self) -> ComposeResult:
yield Header()
hote = ctx["host"].get("label") or ctx["host"]["target"]
yield Static(
f" {t('Proxmox host')} : {hote}"
f" {t('node')} : {ctx.get('node') or '?'}",
id="hostline",
)
with Horizontal(id="body"):
with VerticalScroll(id="fields"):
yield Static(t("Architecture"), classes="grouptitle")
with RadioSet(id="f_arch"):
for a in arches:
label = a if a != "all" else t("all archs")
yield RadioButton(label, value=a == self.arch)
yield Static(t("Catalog"), classes="grouptitle")
yield SelectionList(id="f_catalog")
yield Static(
t("Resources — applied to ALL VMs"),
classes="grouptitle",
)
with RadioSet(id="f_profile"):
for label in ("x1", "x2", "x3", "x4"):
yield RadioButton(label, value=label == "x1")
yield RadioButton(t("custom"))
# Les mêmes trois ressources qu'ailleurs, montées par la
# même fabrique : « libre… » révèle la saisie du dessous.
for champ, presets, etiquette in (
("vcpus", ctx["cpu_presets"], t("vCPU")),
("ram", ctx["ram_presets"], t("RAM: 2048 or 8G")),
("disk", ctx["disk_presets"], t("Disk")),
):
yield Select(
[
(
(
f"{v // 1024}G"
if champ == "ram"
else str(v)
),
v,
)
for v in presets
]
+ [(t("free value…"), FREE)],
prompt=etiquette,
id=RES_FIELDS[champ][0][1:],
disabled=True,
)
yield Input(
placeholder=etiquette,
id=RES_FIELDS[champ][1][1:],
classes="freeval",
disabled=True,
)
yield Static(t("Proxmox VE"), classes="grouptitle")
yield Select(
[(s, s) for s in stockages],
value=(
ctx.get("storage")
or (stockages[0] if stockages else SELECT_NULL)
),
prompt=t("Storage"),
allow_blank=not stockages,
id="f_storage",
)
yield Select(
[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
self._choix_ponts(),
value=(
ctx.get("bridge")
or (ponts[0] if ponts else SELECT_NULL)
),
prompt=t("Bridge"),
allow_blank=not ponts,
id="f_bridge",
)
yield Static(f" {t('First VMID')}")
yield Input(
value=str(ctx.get("next_vmid") or 100),
placeholder="100",
id="f_vmid",
)
yield Static(t("Access"), classes="grouptitle")
yield Static(f" {t('SSH public key')}")
yield Input(
value=ctx.get("ssh_key") or "",
placeholder="~/.ssh/id_ed25519.pub",
id="f_key",
)
yield Checkbox(
t("Start the VM after creating it"),
value=True,
id="f_start",
)
yield Checkbox(
t("Add an entry to ~/.ssh/config"),
value=True,
id="f_sshcfg",
)
[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
yield from self.compose_vm_type()
# La case commande TOUTE installation — ERPLibre, Odoo,
# mais aussi l'hyperviseur Proxmox VE d'une VM imbriquée.
# Nommée « ERPLibre », elle laissait croire qu'un système
# Proxmox s'installerait quand même.
yield Static(
t("Installation"),
id="t_install",
classes="grouptitle",
)
yield Checkbox(
t("Install software in the VM"),
value=True,
id="f_install",
)
yield Select(
[(lbl, i) for i, (lbl, _c) in enumerate(profiles)],
value=0 if profiles else SELECT_NULL,
allow_blank=not profiles,
id="f_profile_install",
)
yield Select(
[(b, b) for b in branches],
[FIX] deploy : la branche du dépôt, et la sortie pendant qu'elle arrive Trois défauts rapportés sur un déploiement Proxmox. La branche proposée était « dependabot/pip/aiobotocore-3.1.3 » : « git ls-remote » rend la liste par ordre alphabétique, et l'écran en prenait la première. Il propose maintenant la branche du DÉPÔT — celle qu'on a sous les yeux — puis develop, puis master ; la liste met ces trois-là en tête et relègue les branches de robot à la fin. Les deux écrans partagent la règle. La vue de progression n'affichait la sortie qu'à la FIN du travail : « subprocess.run » ne rend rien avant. Sur Proxmox, une VM demande le téléchargement de 325 Mio puis l'import du disque — plusieurs minutes de bloc vide. Elle lit maintenant ligne à ligne. Et « s » y ouvre un ssh sur la VM créée, par son nom, donc par l'entrée ~/.ssh/config qui porte le rebond. --- EN --- Three defects reported on a Proxmox deployment. The branch offered was "dependabot/pip/aiobotocore-3.1.3": "git ls-remote" returns the list in alphabetical order and the screen took its first entry. It now offers the CHECKOUT's branch — the one in front of you — then develop, then master; the list puts those three first and pushes robot branches to the end. Both screens share the rule. The progress view only showed output at the END of a job: "subprocess.run" returns nothing before that. On Proxmox a VM needs a 325 MiB download then a disk import — minutes of an empty block. It now reads line by line. And "s" opens an ssh to the created VM, by name, hence through the ~/.ssh/config entry that carries the jump. Assisted-by: Claude Opus 5
2026-08-24 05:38:28 -04:00
value=branch_default(
branches, ctx.get("branch_current")
),
allow_blank=False,
id="f_branch",
)
[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
yield from self.compose_install_extras()
yield from self.compose_timezone()
yield from self.compose_python()
# Hors de la section « Installation » : le suivi regarde la
# VM ARRIVER, même quand rien ne s'installe. Rangé dedans,
# il se serait grisé avec elle.
yield Static(
t("Monitoring and parallelism"),
classes="grouptitle",
)
yield Checkbox(
t("Follow the installation (dashboard)"),
value=True,
id="f_monitor",
)
yield Static(f" {t('Parallelism')}")
[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
# Cochée, la case donne une exécution PAR installation :
# le plafond du nombre de CPU ne s'applique plus.
# Décochée, le nombre reprend la main, et son défaut suit
# l'HÔTE PROXMOX — l'écran restait figé à quatre choix et
# en proposait un, quel que soit le nombre de cœurs.
yield Checkbox(
t("One run per install"),
value=True,
id="f_par_all",
)
yield Select(
[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
[(str(n), n) for n in range(1, ctx["host_cpu"] + 1)],
value=ctx["host_cpu"],
allow_blank=False,
[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
disabled=True,
id="f_par",
)
with Vertical(id="right"):
yield VerticalScroll(id="plan")
yield Static("", id="totals")
with Horizontal(id="actions"):
yield Button(t("Deploy"), variant="primary", id="go")
yield Button(t("Text prompts"), id="prompts")
yield Button(t("Cancel"), id="no")
yield Footer()
[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 _choix_ponts(self):
"""Les ponts de l'hôte, plus « en créer un ».
L'entrée de création reste offerte même quand des ponts existent :
sur un hôte qui n'en a qu'un, sur le LAN, on peut vouloir un
réseau interne pour un parc d'essai."""
choix = [(b, b) for b in self._ponts]
if ctx.get("make_bridge"):
nom, cidr = ctx.get("internal_bridge") or ("vmbr0", "")
choix.append(
(
f"➕ {t('create an internal')} {nom} ({cidr}) + NAT",
CREER_PONT,
)
)
return choix
def _creer_pont(self) -> None:
"""Crée le pont sur l'hôte, dans un FIL : l'appel dure des
secondes, et l'écran doit rester vivant pendant ce temps."""
self.notify(t("Creating the bridge on the host…"))
def travail():
nom, raison = ctx["make_bridge"]()
self.call_from_thread(self._pont_cree, nom, raison)
self.run_worker(travail, thread=True)
def _pont_cree(self, nom, raison) -> None:
"""Retour du fil. Le sélecteur est remis d'aplomb dans les DEUX
cas : laissé sur « créer », il ne désignerait aucun pont."""
selecteur = self.query_one("#f_bridge", Select)
if not nom:
self.notify(
f"{t('The bridge did not come up.')} {raison}",
severity="error",
timeout=12,
)
selecteur.value = (
self._ponts[0] if self._ponts else SELECT_NULL
)
return
if nom not in self._ponts:
self._ponts.append(nom)
self._syncing = True
selecteur.set_options(self._choix_ponts())
selecteur.value = nom
self._syncing = False
self.notify(f"✓ {nom}")
self._refresh_after()
def on_mount(self) -> None:
self._reload_catalog()
self._sync_install_deps()
# ---------------------------------------------------------------- #
# Le plan
# ---------------------------------------------------------------- #
def _entries(self):
return catalog.get(self.arch) or []
def _selected_entries(self):
choisis = set(self.query_one("#f_catalog", SelectionList).selected)
return [e for e in self._entries() if entry_key(e) in choisis]
def _presets(self):
return {
"vcpus": ctx["cpu_presets"],
"ram": ctx["ram_presets"],
"disk": ctx["disk_presets"],
}
def _reload_catalog(self) -> None:
liste = self.query_one("#f_catalog", SelectionList)
garde = set(liste.selected)
liste.clear_options()
for e in self._entries():
cle = entry_key(e)
liste.add_option((entry_label(e), cle, cle in garde))
self._recompute()
self._mount_rows()
def _recompute(self) -> None:
entries = self._plan_entries()
self.vms = build_vms(
entries,
self.profile,
ctx["base_vcpus"],
ctx["host_cpu"],
self.custom,
self.overrides,
[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
self._default_desktop(),
desktop_suffixes,
)
# Ce qu'un système IMPOSE d'installer, posé sur le MODÈLE : le
# déploiement lit « install_cmd » VM par VM.
for vm in self.vms:
impose = distro_profiles.get(vm["distro"])
if impose and not vm.get("install_cmd"):
vm["install_label"], vm["install_cmd"] = impose
self.rows = plan_rows(
self.vms, noms_pris, orphelin=lambda _n: False
)
# Le supplément d'ERPLibre ne vaut que pour les VM qui l'auront
# vraiment : une VM Proxmox ne clonera pas le dépôt.
[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
installe = self.query_one("#f_install", Checkbox).value
commun = (self._install() or {}).get("cmd") or ""
outils = self._vm_tools()
for row in self.rows:
cmd_vm = row["vm"].get("install_cmd") or commun
if installe and cmd_vm.strip() not in no_erplibre:
row["disk_gb"] += ctx.get("extra_disk_gb", 0)
# Le bureau et les outils ne pèsent que sur les VM qui les
# reçoivent RÉELLEMENT : Android Studio n'existe qu'en
# x86_64, les extensions GNOME n'ont de sens que sous GNOME.
row["disk_gb"] += self._extras_disk_gb(row["vm"], outils)
for row, entry in zip(self.rows, entries):
cle = entry_key(entry)
row["custom"] = bool(self.overrides.get(cle))
row["locked"] = cle in self.locked
assign_vmids(
self.rows,
vmids_pris,
self._vmid_start(),
lambda vmid: (ctx.get("ipconfig") or (lambda _v: "ip=dhcp"))(
self._bridge(), vmid
),
)
self._render_plan()
[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
self.render_extras()
def _auto_name(self, index):
"""Le nom du catalogue, suffixé du bureau : ce que la VM
reprendrait si on effaçait le sien."""
from script.todo.deploy_form_lib import vm_name
return vm_name(
self._plan_entries()[index]["name"],
self.rows[index]["vm"].get("desktop"),
desktop_suffixes,
)
def _vmid_start(self):
brut = self.query_one("#f_vmid", Input).value.strip()
return (
int(brut) if brut.isdigit() else (ctx.get("next_vmid") or 100)
)
def _bridge(self):
valeur = self.query_one("#f_bridge", Select).value
[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 valeur is SELECT_NULL or valeur == CREER_PONT:
# La sentinelle n'est pas un pont : la rendre ferait déployer
# une VM sur « __creer_pont__ ».
return ""
return valeur
def _storage(self):
valeur = self.query_one("#f_storage", Select).value
return "" if valeur is SELECT_NULL else valeur
def _row_head(self, index, row):
"""La ligne de titre du socle, plus ce que Proxmox ajoute : le
VMID et l'adresse. Les deux sont décidés ICI et pas par l'hôte —
les montrer avant de lancer est le seul moyen de les vérifier."""
base = PlanMixin._row_head(self, index, row)
vm = row["vm"]
if row["state"] == "exists":
return base
adresse = (vm.get("ipconfig") or "").replace("ip=", "")
return f"{base} VMID {vm.get('vmid', '?')} {adresse}"
def _mount_rows(self) -> None:
"""(Re)construit le panneau droit.
Le verrou couvre TOUT le montage : poser « value= » sur un Select
fait émettre un Changed à Textual, que on_select_changed prendrait
pour une saisie."""
self._syncing = True
self._gen += 1
plan = self.query_one("#plan", VerticalScroll)
plan.remove_children()
cartes = []
for i, r in enumerate(self.rows):
vm = r["vm"]
cle = self._row_key(i)
item = self._plan_entries()[i]
rangee = Horizontal(
Button("+", id=f"p{i}", classes="vmcopy"),
Button("✎", id=f"r{i}", classes="vmcopy"),
Button(
"🔒" if cle in self.locked else "🔓",
id=f"l{i}",
variant=(
"success" if cle in self.locked else "default"
),
classes="vmlock",
),
*res_row_widgets(
i,
vm,
self._presets(),
labels={"vcpus": t("vCPU")},
null=SELECT_NULL,
),
(
Button("−", id=f"m{i}", classes="vmcopy")
if item.get("instance")
else Static("", classes="vmcopy")
),
[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
# Branche, profil, type — par VM. On déploie ici le plus
# souvent un parc MIXTE : un hyperviseur Proxmox imbriqué
# à côté de VM ERPLibre, et l'écran n'offrait qu'un choix
# commun pour les deux.
*self.install_row_widgets(i, SELECT_NULL),
classes="vmrow",
)
cartes.append(
Vertical(
Static(self._row_head(i, r), id=f"h{i}"),
rangee,
classes=(
"vmcard locked" if cle in self.locked else "vmcard"
),
)
)
# Marque de génération sur CHAQUE widget : « walk_children() » ne
# voit rien avant le montage, les enfants attendent dans
# « _pending_children ».
def marquer(node):
node._el_gen = self._gen
for child in getattr(node, "_pending_children", None) or []:
marquer(child)
for carte in cartes:
marquer(carte)
plan.mount_all(cartes)
self._shown_ids = self._row_ids()
self.call_after_refresh(self._after_mount_rows)
def _after_mount_rows(self) -> None:
self._sync_free_inputs()
self._syncing = False
def _render_plan(self) -> None:
for i, r in enumerate(self.rows):
try:
self.query_one(f"#h{i}", Static).update(
self._row_head(i, r)
)
except Exception:
pass
n, cpu, ram, disque = plan_totals(self.rows)
libre = ctx.get("free_ram") or 0
# La place du stockage CHOISI : elle change avec la liste, donc
# elle se relit à chaque rendu plutôt qu'une fois au montage.
place = gib((ctx.get("storage_avail") or {}).get(self._storage()))
alertes = []
if libre and ram > libre:
alertes.append(t("more RAM than the host has free"))
if place and disque > place:
alertes.append(t("more disk than the storage has free"))
alerte = f" ⚠ {' · '.join(alertes)}" if alertes else ""
self.query_one("#totals", Static).update(
f" {n} {t('VM')} {cpu} vCPU {ram} Mo RAM "
f"{disk_note(disque, place)} {res_label(self.profile)}"
f" {t('storage')} {self._storage() or '?'}"
f" {t('bridge')} {self._bridge() or '?'}{alerte}"
)
def _refresh_after(self, remonter=False) -> None:
"""Recalcule, et ne remonte les rangées que si le JEU a changé :
un remontage à chaque frappe volerait le focus."""
self._recompute()
if remonter or self._row_ids() != self._shown_ids:
self._mount_rows()
else:
self._sync_free_inputs()
# ---------------------------------------------------------------- #
# Les messages
# ---------------------------------------------------------------- #
def on_selection_list_selected_changed(self, _event) -> None:
self._refresh_after()
def on_radio_set_changed(self, event) -> None:
if event.radio_set.id == "f_arch":
self.arch = arches[event.index]
self._reload_catalog()
return
[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 event.radio_set.id in ("f_type", "f_store"):
# Le bureau pèse sur le disque et change le nom des VM.
self._refresh_after(remonter=True)
return
if event.radio_set.id == "f_profile":
choix = ("1", "2", "3", "4", "custom")[event.index]
self.profile = choix
sur_mesure = choix == "custom"
for champ in RES_FIELDS:
self.query_one(RES_FIELDS[champ][0], Select).disabled = (
not sur_mesure
)
if not sur_mesure:
self._show_free(champ, False)
# Un réglage commun reprend la main sur les VM non figées :
# c'est le sens même du mot « commun ».
self._clear_overrides(tuple(RES_FIELDS))
self._refresh_after(remonter=True)
def on_checkbox_changed(self, event) -> None:
if event.checkbox.id == "f_install":
# Le disque d'ERPLibre entre — ou sort — du total.
self._sync_install_deps()
self._refresh_after()
[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
elif event.checkbox.id == "f_par_all":
self.query_one("#f_par", Select).disabled = event.value
elif str(event.checkbox.id or "").startswith("f_tool_"):
# Un IDE de plus, c'est un disque plus grand : le plan doit
# le montrer AVANT de déployer, pas après une heure.
self._refresh_after()
def on_input_changed(self, event) -> None:
if event.input.id == "f_vmid":
self._refresh_after()
def on_input_submitted(self, event) -> None:
ident = event.input.id or ""
if ident in {RES_FIELDS[c][1][1:] for c in RES_FIELDS}:
self._apply_free(ident.split("_", 1)[1])
self._refresh_after(remonter=True)
return
if ident.startswith("c") and "_" in ident:
rang, champ = ident[1:].split("_", 1)
if rang.isdigit():
self._set_override(
int(rang), champ, self._read_row_free(int(rang), champ)
)
self._refresh_after()
def on_select_changed(self, event) -> None:
if self._syncing:
return
[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 self.extras_on_select(event):
return
ident = event.select.id or ""
# La marque de génération ne concerne QUE les widgets de rangée :
# un widget global n'en porte pas. L'exiger de tous revenait à
# ignorer chaque réglage commun — mesuré, ni le stockage, ni la
# RAM générale n'atteignaient le plan.
if re.match(r"v\d+_", ident) and not self._is_current(
event.select
):
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
if ident == "f_bridge" and event.value == CREER_PONT:
self._creer_pont()
return
if ident in ("f_storage", "f_bridge"):
self._refresh_after()
return
[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
if ident in ("f_branch", "f_profile_install"):
# Un réglage commun reprend la main sur les VM non figées :
# c'est le sens même du mot « commun ».
self._clear_overrides(
("branch",) if ident == "f_branch" else ("install_cmd",)
)
self._refresh_after(remonter=True)
return
# Réglage commun : « libre… » révèle la saisie, une valeur
# s'applique à toutes les VM non figées.
champ = SELECT_TO_FIELD.get(ident)
if champ:
if event.value is FREE:
self._show_free(champ, True)
self.query_one(RES_FIELDS[champ][1], Input).focus()
elif event.value is not SELECT_NULL:
self._show_free(champ, False)
self.custom[champ] = event.value
self._clear_overrides((champ,))
self._refresh_after(remonter=True)
return
# Réglage d'UNE rangée.
if ident.startswith("v") and "_" in ident:
rang, champ = ident[1:].split("_", 1)
[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
if not rang.isdigit():
return
index = int(rang)
[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
if index >= len(self.rows):
return
if self.extras_on_row_select(event, index, champ):
self._refresh_after()
return
if champ not in RES_FIELDS:
return
if event.value is FREE:
self._row_free(index, champ, True)
self._set_override(
index, champ, self._read_row_free(index, champ)
)
elif event.value is not SELECT_NULL:
# L'écho du montage n'est pas une saisie : sans ce test,
# les trois champs de chaque VM se surchargeaient dès
# l'affichage et toutes les rangées portaient la marque ✎.
if self._row_echo(index, champ, event.value):
return
self._row_free(index, champ, False)
self._set_override(index, champ, event.value)
self._refresh_after()
def on_button_pressed(self, event) -> None:
ident = event.button.id or ""
if ident == "go":
self.action_deploy()
elif ident == "no":
self.action_cancel()
elif ident == "prompts":
# Retour aux invites textuelles : {} n'est pas None, et
# l'appelant sait faire la différence entre « annulé » et
# « pose-moi les questions à l'ancienne ».
self.result = {}
self.exit()
elif ident.startswith("p") and ident[1:].isdigit():
self._add_copy(int(ident[1:]), 1)
elif ident.startswith("m") and ident[1:].isdigit():
self._add_copy(int(ident[1:]), -1)
elif ident.startswith("r") and ident[1:].isdigit():
self._rename(int(ident[1:]))
elif ident.startswith("l") and ident[1:].isdigit():
index = int(ident[1:])
self._set_lock(index, self._row_key(index) not in self.locked)
# ---------------------------------------------------------------- #
# Les actions
# ---------------------------------------------------------------- #
def _install(self):
if not self.query_one("#f_install", Checkbox).value:
return None
index = self.query_one("#f_profile_install", Select).value
label, cmd = (
profiles[index]
if profiles and isinstance(index, int)
else ("", "")
)
return {
"branch": self.query_one("#f_branch", Select).value,
[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
# /opt et service confiné, comme en QEMU/KVM : le choix ne
# regarde pas l'hyperviseur, il regarde le système invité.
"prod": self.query_one("#f_prod", Checkbox).value,
"label": label,
"cmd": cmd,
}
def _form_values(self):
cle = self.query_one("#f_key", Input).value.strip()
return {
"host": ctx["host"],
"storage": self._storage(),
"bridge": self._bridge(),
[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": ctx.get("nameservers") or (),
"res_label": res_label(self.profile),
"ssh_key": os.path.expanduser(cle) if cle else "",
"start": self.query_one("#f_start", Checkbox).value,
"add_ssh_config": self.query_one("#f_sshcfg", Checkbox).value,
"install": self._install(),
[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
**self.extras_values(),
# Le suivi est demandé au NIVEAU DU DÉPLOIEMENT : une VM sans
# ERPLibre se suit aussi (cloud-init, puis relevé système).
"monitor": self.query_one("#f_monitor", Checkbox).value,
[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
# Une exécution par installation : le nombre de VM retenues
# fait foi. Le déploiement le borne ensuite à ce même nombre,
# donc une valeur haute ne crée aucun travailleur inutile.
"parallelism": (
max(1, len(self.vms))
if self.query_one("#f_par_all", Checkbox).value
else self.query_one("#f_par", Select).value
),
}
def action_deploy(self) -> None:
spec = build_spec(self.vms, noms_pris, self._form_values())
if not spec["vms"]:
self.notify(t("Nothing to deploy."), severity="warning")
return
if not spec["storage"]:
self.notify(
t("No storage able to hold a VM disk."), severity="error"
)
return
if not spec["bridge"]:
self.notify(t("No bridge on the host."), severity="error")
return
self.result = spec
self.exit()
def action_preview(self) -> None:
build = ctx.get("build_command")
if not build:
return
spec = build_spec(self.vms, noms_pris, self._form_values())
lignes = ["\n".join(build(vm, spec)) for vm in spec["vms"]] or [
t("Nothing selected.")
]
self.push_screen(preview_screen()(lignes))
def action_clear_vm(self) -> None:
"""Rend au réglage commun la VM sous le curseur (et son verrou)."""
index = self._focused_row()
if index is None:
return
cle = self._row_key(index)
self.locked.discard(cle)
self.overrides.pop(cle, None)
self._refresh_after(remonter=True)
def action_cancel(self) -> None:
self.result = None
self.exit()
app = ProxmoxForm()
if not run_app:
return app
app.run()
return app.result