[FIX] déploiement : suivre une VM même sans installation ERPLibre
Décocher l'installation d'ERPLibre faisait disparaître le tableau de bord. La
case « suivi » vivait DANS le groupe de l'installation, build_spec ne la
recopiait même pas dans la spec, et l'épilogue était gardé par « if install or
desktop » : sans rien à installer, il ne se passait rien.
Le suivi devient un choix du DÉPLOIEMENT. Et sans rien à installer, la
commande distante ne vaut plus « true » — journal vide, ✅ instantané : elle
regarde la VM ARRIVER, attend cloud-init, puis relève système, noyau, adresse,
disque et mémoire. Le journal cesse aussi d'annoncer une installation ERPLibre
qui n'a pas lieu.
--- EN ---
Unchecking the ERPLibre install made the dashboard vanish. The "monitoring"
checkbox lived INSIDE the install group, build_spec did not even copy it into
the spec, and the deploy epilogue was gated by "if install or desktop": with
nothing to install, nothing happened.
Monitoring is now a DEPLOYMENT-level choice. And with nothing to install, the
remote command is no longer "true" — empty log, instant ✅: it watches the VM
ARRIVE, waits for cloud-init, then reports system, kernel, address, disk and
memory. The log also stops announcing an ERPLibre install that never happens.
Assisted-by: Claude Opus 5
2026-08-23 03:50:57 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
"""Suivre un déploiement qui n'installe PAS ERPLibre.
|
|
|
|
|
|
|
|
|
|
Symptôme rapporté : en décochant l'installation d'ERPLibre, le tableau de bord
|
|
|
|
|
ne s'ouvrait plus du tout. Deux causes, l'une derrière l'autre :
|
|
|
|
|
|
|
|
|
|
- la case « suivi » vivait DANS le groupe de l'installation ERPLibre, et
|
|
|
|
|
`build_spec` ne la recopiait même pas dans la spec finale ;
|
|
|
|
|
- l'épilogue du déploiement était gardé par « if install or desktop » : sans
|
|
|
|
|
rien à installer, il ne se passait rien.
|
|
|
|
|
|
|
|
|
|
Et si le suivi s'ouvrait quand même, il n'aurait rien montré : la commande
|
|
|
|
|
distante valait « true », donc un journal vide et un ✅ instantané. Elle
|
|
|
|
|
regarde maintenant la VM ARRIVER — cloud-init, puis un relevé système — ce qui
|
|
|
|
|
donne un début, une fin, et de quoi juger qu'elle est prête.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import contextlib
|
|
|
|
|
import io
|
|
|
|
|
import os
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
import unittest
|
|
|
|
|
|
|
|
|
|
sys.argv = ["todo.py"]
|
|
|
|
|
from script.todo import qemu_install_monitor as mon # noqa: E402
|
|
|
|
|
from script.todo.qemu_deploy_form import build_spec # noqa: E402
|
|
|
|
|
from script.todo.todo import TODO # noqa: E402
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestLaCommandeDistante(unittest.TestCase):
|
|
|
|
|
def setUp(self):
|
|
|
|
|
self.todo = TODO.__new__(TODO)
|
|
|
|
|
|
|
|
|
|
def _sans_rien(self):
|
|
|
|
|
return self.todo._qemu_erplibre_remote_cmd(None)
|
|
|
|
|
|
|
|
|
|
def test_it_is_no_longer_a_bare_true(self):
|
|
|
|
|
"""« true » rendait un journal vide et un ✅ instantané : le suivi
|
|
|
|
|
s'ouvrait sur rien."""
|
|
|
|
|
self.assertNotEqual("true", self._sans_rien().strip())
|
|
|
|
|
|
|
|
|
|
def test_it_is_valid_shell(self):
|
|
|
|
|
res = subprocess.run(
|
|
|
|
|
["bash", "-n"],
|
|
|
|
|
input=self._sans_rien(),
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(0, res.returncode, res.stderr)
|
|
|
|
|
|
|
|
|
|
def test_it_waits_for_cloud_init(self):
|
|
|
|
|
"""C'est le vrai « suivi » d'une VM neuve : tant que cloud-init tourne,
|
|
|
|
|
la machine n'est pas prête."""
|
|
|
|
|
self.assertIn("cloud-init status --wait", self._sans_rien())
|
|
|
|
|
|
|
|
|
|
def test_it_frames_the_step_so_the_monitor_can_bound_it(self):
|
|
|
|
|
"""Le tableau lit ces marqueurs pour dire où en est la VM."""
|
|
|
|
|
cmd = self._sans_rien()
|
|
|
|
|
self.assertIn("===>", cmd)
|
|
|
|
|
self.assertIn("<===", cmd)
|
|
|
|
|
|
|
|
|
|
def test_it_reports_what_says_the_vm_is_up(self):
|
|
|
|
|
cmd = self._sans_rien()
|
|
|
|
|
for morceau in ("/etc/os-release", "uname -r", "hostname -I", "df -h"):
|
|
|
|
|
self.assertIn(morceau, cmd, morceau)
|
|
|
|
|
|
|
|
|
|
def test_it_runs_and_says_something(self):
|
|
|
|
|
"""Exécutée ici, hors VM : l'attente de cloud-init est neutralisée, le
|
|
|
|
|
relevé doit sortir et rendre 0."""
|
|
|
|
|
cmd = self._sans_rien().replace(
|
|
|
|
|
"sudo timeout 900 cloud-init status --wait", "true"
|
|
|
|
|
)
|
|
|
|
|
res = subprocess.run(
|
|
|
|
|
["bash", "-c", cmd], capture_output=True, text=True, timeout=60
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(0, res.returncode, res.stderr[-300:])
|
|
|
|
|
self.assertIn("===>", res.stdout)
|
|
|
|
|
self.assertIn("<===", res.stdout)
|
|
|
|
|
# Trois lignes de relevé au moins : sans elles, le journal est creux.
|
|
|
|
|
self.assertGreaterEqual(len(res.stdout.strip().splitlines()), 5)
|
|
|
|
|
|
|
|
|
|
def test_a_desktop_only_vm_still_installs_its_desktop(self):
|
|
|
|
|
"""Le chemin qui marchait déjà ne doit pas changer de sens."""
|
|
|
|
|
self.todo._qemu_desktop_remote_cmd = lambda d, s: "INSTALLE_BUREAU; "
|
|
|
|
|
self.todo._qemu_tools_remote_cmd = lambda *a, **k: ""
|
|
|
|
|
self.todo._qemu_no_auto_upgrade = lambda *a, **k: ""
|
|
|
|
|
cmd = self.todo._qemu_erplibre_remote_cmd(None, desktop="gnome")
|
|
|
|
|
self.assertIn("INSTALLE_BUREAU", cmd)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestLaSpecDuFormulaire(unittest.TestCase):
|
|
|
|
|
def _form(self, **extra):
|
|
|
|
|
base = {
|
|
|
|
|
"res_label": "x1",
|
|
|
|
|
"ssh_key": "/k.pub",
|
|
|
|
|
"install": None,
|
|
|
|
|
"add_ssh_config": False,
|
|
|
|
|
"parallelism": 1,
|
|
|
|
|
}
|
|
|
|
|
base.update(extra)
|
|
|
|
|
return base
|
|
|
|
|
|
|
|
|
|
def test_the_choice_reaches_the_spec(self):
|
|
|
|
|
"""Il ne la recopiait pas : le choix du formulaire n'atteignait jamais
|
|
|
|
|
le déploiement."""
|
|
|
|
|
spec = build_spec([], [], self._form(monitor=True))
|
|
|
|
|
self.assertTrue(spec["monitor"])
|
|
|
|
|
spec = build_spec([], [], self._form(monitor=False))
|
|
|
|
|
self.assertFalse(spec["monitor"])
|
|
|
|
|
|
|
|
|
|
def test_an_old_form_without_the_key_still_monitors(self):
|
|
|
|
|
"""Compatibilité : une spec enregistrée avant ce changement ne doit pas
|
|
|
|
|
perdre son tableau de bord."""
|
|
|
|
|
self.assertTrue(build_spec([], [], self._form())["monitor"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestLaDecisionDuDeploiement(unittest.TestCase):
|
|
|
|
|
"""L'épilogue : qui est appelé, et avec quoi."""
|
|
|
|
|
|
|
|
|
|
def _joue(self, spec):
|
|
|
|
|
todo = TODO.__new__(TODO)
|
|
|
|
|
appels = []
|
|
|
|
|
todo._qemu_install_erplibre_monitored = lambda *a, **k: appels.append(
|
|
|
|
|
"suivi"
|
|
|
|
|
)
|
|
|
|
|
todo._qemu_install_erplibre_vm = lambda *a, **k: appels.append("muet")
|
|
|
|
|
todo._qemu_resolve_ips = lambda names, labels=None: {}
|
[ADD] cache qemu : miroir des téléchargements des VM, hors ligne compris
VMs of one host pulled the same packages again and again, and an install
could not be proven to work without the network. A Go service intercepts the
bridge transparently: a package file is served from disk, an index is always
revalidated upstream and replayed only when upstream is mute, git is mirrored.
Deployment poses the cache authority in QEMU and Proxmox VE guests, and can cut
the network for a whole install. The TODO menu installs, diagnoses, fills,
cleans and copies the store; long_test/qemu_cache.py measures that a second VM
pulls no package upstream. Covered by Go tests and test/test_qemu_cache_*.py.
--- FR ---
Les VM d'un hôte tiraient sans cesse les mêmes paquets, et rien ne prouvait
qu'une installation marche sans réseau. Un service Go intercepte le pont de
façon transparente : un fichier de paquet est servi du disque, un index est
toujours revalidé à l'amont et rejoué seulement quand l'amont est muet, git
est mis en miroir. Le déploiement pose l'autorité du cache dans les invités
QEMU et Proxmox VE, et peut couper le réseau pour toute une installation. Le
menu TODO installe, diagnostique, remplit, nettoie et emporte le magasin ;
long_test/qemu_cache.py mesure qu'une seconde VM ne tire aucun paquet de
l'amont. Couvert par les tests Go et test/test_qemu_cache_*.py.
Assisted-by: Claude Opus 5
2026-09-14 14:41:19 -04:00
|
|
|
# Même en ligne, le déploiement relit l'état de la coupure : un hôte
|
|
|
|
|
# simulé sans guet et un verrou introuvable tiennent la machine de
|
|
|
|
|
# test hors du jeu.
|
|
|
|
|
import tempfile
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
tmp = tempfile.TemporaryDirectory()
|
|
|
|
|
self.addCleanup(tmp.cleanup)
|
|
|
|
|
todo._qemu_shell = lambda cmd, timeout=60: 1
|
|
|
|
|
todo._qemu_verrou_hors_ligne_chemin = lambda: Path(tmp.name) / "absent"
|
[FIX] déploiement : suivre une VM même sans installation ERPLibre
Décocher l'installation d'ERPLibre faisait disparaître le tableau de bord. La
case « suivi » vivait DANS le groupe de l'installation, build_spec ne la
recopiait même pas dans la spec, et l'épilogue était gardé par « if install or
desktop » : sans rien à installer, il ne se passait rien.
Le suivi devient un choix du DÉPLOIEMENT. Et sans rien à installer, la
commande distante ne vaut plus « true » — journal vide, ✅ instantané : elle
regarde la VM ARRIVER, attend cloud-init, puis relève système, noyau, adresse,
disque et mémoire. Le journal cesse aussi d'annoncer une installation ERPLibre
qui n'a pas lieu.
--- EN ---
Unchecking the ERPLibre install made the dashboard vanish. The "monitoring"
checkbox lived INSIDE the install group, build_spec did not even copy it into
the spec, and the deploy epilogue was gated by "if install or desktop": with
nothing to install, nothing happened.
Monitoring is now a DEPLOYMENT-level choice. And with nothing to install, the
remote command is no longer "true" — empty log, instant ✅: it watches the VM
ARRIVE, waits for cloud-init, then reports system, kernel, address, disk and
memory. The log also stops announcing an ERPLibre install that never happens.
Assisted-by: Claude Opus 5
2026-08-23 03:50:57 -04:00
|
|
|
base = {
|
|
|
|
|
"vms": [],
|
|
|
|
|
"existing": ["vm-a"],
|
|
|
|
|
"install": None,
|
|
|
|
|
"add_ssh_config": False,
|
|
|
|
|
"parallelism": 1,
|
|
|
|
|
}
|
|
|
|
|
base.update(spec)
|
|
|
|
|
with contextlib.redirect_stdout(io.StringIO()):
|
|
|
|
|
todo._qemu_run_spec(base)
|
|
|
|
|
return appels
|
|
|
|
|
|
|
|
|
|
def test_without_erplibre_the_monitor_still_opens(self):
|
|
|
|
|
"""Le cœur du problème rapporté."""
|
|
|
|
|
self.assertEqual(["suivi"], self._joue({"monitor": True}))
|
|
|
|
|
|
|
|
|
|
def test_a_spec_without_the_key_monitors_too(self):
|
|
|
|
|
self.assertEqual(["suivi"], self._joue({}))
|
|
|
|
|
|
|
|
|
|
def test_refusing_the_monitor_does_nothing_at_all(self):
|
|
|
|
|
"""Et surtout : ne pas partir installer un profil qui n'existe pas.
|
|
|
|
|
L'ancien repli faisait « install['cmd'] » sur un None."""
|
|
|
|
|
self.assertEqual([], self._joue({"monitor": False}))
|
|
|
|
|
|
|
|
|
|
def test_an_install_without_the_monitor_takes_the_quiet_path(self):
|
|
|
|
|
appels = self._joue(
|
|
|
|
|
{
|
|
|
|
|
"monitor": False,
|
|
|
|
|
"install": {
|
|
|
|
|
"branch": "develop",
|
|
|
|
|
"prod": False,
|
|
|
|
|
"cmd": "make x",
|
|
|
|
|
"monitor": False,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(["muet"], appels)
|
|
|
|
|
|
|
|
|
|
def test_the_install_keeps_the_last_word_on_its_own_monitoring(self):
|
|
|
|
|
appels = self._joue(
|
|
|
|
|
{
|
|
|
|
|
"monitor": False,
|
|
|
|
|
"install": {
|
|
|
|
|
"branch": "develop",
|
|
|
|
|
"prod": False,
|
|
|
|
|
"cmd": "make x",
|
|
|
|
|
"monitor": True,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(["suivi"], appels)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestLeJournal(unittest.TestCase):
|
|
|
|
|
"""L'en-tête et le prologue ne doivent pas annoncer ce qui n'a pas lieu."""
|
|
|
|
|
|
|
|
|
|
def _vm(self):
|
|
|
|
|
return {
|
|
|
|
|
"name": "vm-a",
|
|
|
|
|
"ip": "10.0.0.9",
|
|
|
|
|
"distro": "debian",
|
|
|
|
|
"version": "13",
|
|
|
|
|
"arch": "amd64",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def test_an_install_is_titled_an_install(self):
|
|
|
|
|
head = mon._log_header(self._vm(), "develop", "2026-01-01 00:00:00")
|
|
|
|
|
self.assertIn(mon.t("installation"), head)
|
|
|
|
|
self.assertIn("develop", head)
|
|
|
|
|
|
|
|
|
|
def test_without_a_branch_it_is_not_called_an_install(self):
|
|
|
|
|
"""« ERPLibre — installation » puis « Branche : » vide, sur un
|
|
|
|
|
déploiement qui n'installe rien : le journal se contredisait."""
|
|
|
|
|
head = mon._log_header(self._vm(), "", "2026-01-01 00:00:00")
|
|
|
|
|
self.assertIn(mon.t("VM start-up"), head)
|
|
|
|
|
self.assertNotIn("Branche", head)
|
|
|
|
|
|
[FIX] cache et journal : hôte imbriqué soustrait, décision écrite
Un invité sans magasin de confiance ne peut RIEN recevoir, et sur un hôte
Proxmox c'est l'HÔTE qu'il faut excepter : imbriqué, l'invité sort masqué
derrière lui et le pont ne voit jamais sa propre adresse. Mesuré depuis
l'invité : code 000 et vérification SSL 19, puis 200 et 0. Sans cela le
gestionnaire de paquets se rabattait sur 564 dérivations à construire.
Le journal par VM ne portait AUCUN message du menu : la cause vivait sur
une console qui défile, et le fichier qu'on rouvre après l'échec n'avait
que le symptôme. Et « db_drop_all » annonçait détruites des bases qui ne
l'étaient pas, son code de retour jeté.
--- EN ---
A guest with no trust store can receive NOTHING, and on a Proxmox host it
is the HOST that must be exempted: nested, the guest leaves masqueraded
behind it and the bridge never sees its own address. Measured from the
guest: code 000 and SSL verification 19, then 200 and 0. Without it the
package manager fell back to building 564 derivations.
The per-VM log carried NO message from the menu: the cause lived on a
console that scrolls away, and the file reopened after failure held only
the symptom. And "db_drop_all" announced databases as dropped that were
not, its exit status discarded.
Assisted-by: Claude Opus 5
2026-09-16 00:43:40 -04:00
|
|
|
def test_what_the_host_decided_reaches_the_log(self):
|
|
|
|
|
"""La console qui a dit la décision a défilé ; le journal est ce
|
|
|
|
|
qu'on rouvre quand l'installation a échoué. Sans ces lignes il ne
|
|
|
|
|
porte que le symptôme — des centaines de lignes de construction et
|
|
|
|
|
un certificat refusé, sans un mot sur ce qui l'a voulu."""
|
|
|
|
|
vm = dict(
|
|
|
|
|
self._vm(),
|
|
|
|
|
notes=[
|
|
|
|
|
"⚠ pas de magasin de confiance : nixos",
|
|
|
|
|
"✓ hôte soustrait au cache : pve-local",
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
head = mon._log_header(vm, "develop", "2026-01-01 00:00:00")
|
|
|
|
|
self.assertIn(mon.t("Prepared by the host:"), head)
|
|
|
|
|
self.assertIn("pas de magasin de confiance", head)
|
|
|
|
|
self.assertIn("hôte soustrait au cache", head)
|
|
|
|
|
|
|
|
|
|
def test_a_log_without_notes_is_unchanged(self):
|
|
|
|
|
"""Le cas ordinaire ne gagne pas une section vide."""
|
|
|
|
|
head = mon._log_header(self._vm(), "develop", "2026-01-01 00:00:00")
|
|
|
|
|
self.assertNotIn(mon.t("Prepared by the host:"), head)
|
|
|
|
|
|
[FIX] déploiement : suivre une VM même sans installation ERPLibre
Décocher l'installation d'ERPLibre faisait disparaître le tableau de bord. La
case « suivi » vivait DANS le groupe de l'installation, build_spec ne la
recopiait même pas dans la spec, et l'épilogue était gardé par « if install or
desktop » : sans rien à installer, il ne se passait rien.
Le suivi devient un choix du DÉPLOIEMENT. Et sans rien à installer, la
commande distante ne vaut plus « true » — journal vide, ✅ instantané : elle
regarde la VM ARRIVER, attend cloud-init, puis relève système, noyau, adresse,
disque et mémoire. Le journal cesse aussi d'annoncer une installation ERPLibre
qui n'a pas lieu.
--- EN ---
Unchecking the ERPLibre install made the dashboard vanish. The "monitoring"
checkbox lived INSIDE the install group, build_spec did not even copy it into
the spec, and the deploy epilogue was gated by "if install or desktop": with
nothing to install, nothing happened.
Monitoring is now a DEPLOYMENT-level choice. And with nothing to install, the
remote command is no longer "true" — empty log, instant ✅: it watches the VM
ARRIVE, waits for cloud-init, then reports system, kernel, address, disk and
memory. The log also stops announcing an ERPLibre install that never happens.
Assisted-by: Claude Opus 5
2026-08-23 03:50:57 -04:00
|
|
|
def test_the_prologue_says_what_actually_follows(self):
|
|
|
|
|
"""« installation ERPLibre en cours » alors que rien ne s'installe."""
|
|
|
|
|
import tempfile
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
vus = []
|
|
|
|
|
vrai_launch, vrai_dir = mon._launch_one, mon.session_dir
|
[FIX] proxmox : l'installation partait sur la mauvaise machine
Rapporté, et c'est le plus grave de la série. Une VM déployée sur Proxmox
sous le nom « erplibre-ubuntu-2604 » — nom déjà porté par un domaine LOCAL —
a vu son installation d'ERPLibre + Odoo partir sur la VM locale. Deux causes
enchaînées : l'entrée ~/.ssh/config volait l'alias de la locale, et le
lanceur détaché ré-résout l'adresse par virsh à chaque tour, qui a répondu
avec le domaine homonyme. Le journal l'écrivait — « → 192.168.123.118 » —
sans que rien n'alerte.
Une VM distante n'est plus ré-résolue : son alias est la seule vérité,
puisqu'il porte le rebond. Et son alias suit la convention des VM imbriquées,
« hôte+vm », le nom court n'étant ajouté que s'il est libre — l'écran le dit.
S'y ajoute le sommaire final qui manquait, à l'image de QEMU/KVM : ce qui
existe, son adresse, sa commande ssh, son journal.
--- EN ---
Reported, and the worst of the series. A VM deployed on Proxmox under the
name "erplibre-ubuntu-2604" — a name already held by a LOCAL domain — had its
ERPLibre + Odoo install land on the local VM. Two chained causes: the
~/.ssh/config entry stole the local one's alias, and the detached launcher
re-resolves the address through virsh on every pass, which answered with the
homonymous domain. The log said so — "→ 192.168.123.118" — with nothing to
raise an alarm.
A remote VM is no longer re-resolved: its alias is the only truth, since it
carries the jump. And its alias follows the nested-VM convention, "host+vm",
the short name being added only when free — the screen says so. Plus the
final summary that was missing, mirroring QEMU/KVM: what exists, its address,
its ssh command, its log.
Assisted-by: Claude Opus 5
2026-08-24 06:37:56 -04:00
|
|
|
# « **kw » et non une liste figée : chaque paramètre ajouté au
|
|
|
|
|
# lanceur (comme « pve ») casserait sinon ce test, qui ne vérifie
|
|
|
|
|
# pourtant que le prologue du journal.
|
[REF] format : passer l'outillage et les tests sous ruff
Le formateur de ce dépôt est ruff depuis qu'il remplace black, qui ne connaît
aucune cible au-delà de py313 ; ce passage applique sa norme à l'arbre entier,
d'un coup, pour qu'aucun commit de fond n'ait à porter du style. L'écart tient
presque entièrement aux chaînes coupées à la main que ruff recolle quand elles
tiennent sur une ligne, et aux « with » multiples qu'il regroupe : aucune
valeur ne change, et les clés de traduction non plus.
Vérifié : la suite unitaire reste verte après le passage, et le contrôle de
syntaxe ne signale rien.
--- EN ---
This repository's formatter is ruff since it replaced black, which knows no
target beyond py313; this pass applies its standard to the whole tree at once,
so that no substantive commit has to carry style. The difference is almost
entirely the hand-split strings ruff joins back when they fit on one line, and
the multiple "with" it merges: no value changes, nor do the translation keys.
Checked: the unit suite stays green after the pass, and the syntax check
reports nothing.
Assisted-by: Claude Opus 5
2026-09-24 13:30:31 -04:00
|
|
|
mon._launch_one = lambda ip, cmd, log, name="", installs=True, **kw: (
|
|
|
|
|
vus.append(installs)
|
[FIX] déploiement : suivre une VM même sans installation ERPLibre
Décocher l'installation d'ERPLibre faisait disparaître le tableau de bord. La
case « suivi » vivait DANS le groupe de l'installation, build_spec ne la
recopiait même pas dans la spec, et l'épilogue était gardé par « if install or
desktop » : sans rien à installer, il ne se passait rien.
Le suivi devient un choix du DÉPLOIEMENT. Et sans rien à installer, la
commande distante ne vaut plus « true » — journal vide, ✅ instantané : elle
regarde la VM ARRIVER, attend cloud-init, puis relève système, noyau, adresse,
disque et mémoire. Le journal cesse aussi d'annoncer une installation ERPLibre
qui n'a pas lieu.
--- EN ---
Unchecking the ERPLibre install made the dashboard vanish. The "monitoring"
checkbox lived INSIDE the install group, build_spec did not even copy it into
the spec, and the deploy epilogue was gated by "if install or desktop": with
nothing to install, nothing happened.
Monitoring is now a DEPLOYMENT-level choice. And with nothing to install, the
remote command is no longer "true" — empty log, instant ✅: it watches the VM
ARRIVE, waits for cloud-init, then reports system, kernel, address, disk and
memory. The log also stops announcing an ERPLibre install that never happens.
Assisted-by: Claude Opus 5
2026-08-23 03:50:57 -04:00
|
|
|
)
|
|
|
|
|
# session_dir détournée : sans cela le test écrivait de VRAIES sessions
|
|
|
|
|
# dans ~/.erplibre/qemu-install, qui polluaient l'historique que
|
|
|
|
|
# « Rouvrir le suivi » propose à l'utilisateur.
|
|
|
|
|
tmp = tempfile.TemporaryDirectory()
|
|
|
|
|
self.addCleanup(tmp.cleanup)
|
|
|
|
|
mon.session_dir = lambda: Path(tmp.name)
|
|
|
|
|
try:
|
|
|
|
|
mon.launch_installs([self._vm()], "", "true")
|
|
|
|
|
mon.launch_installs([self._vm()], "develop", "true")
|
|
|
|
|
finally:
|
|
|
|
|
mon._launch_one, mon.session_dir = vrai_launch, vrai_dir
|
|
|
|
|
self.assertEqual([False, True], vus)
|
|
|
|
|
|
|
|
|
|
def test_the_host_key_notice_is_not_a_warning(self):
|
|
|
|
|
"""ssh l'écrit à CHAQUE première connexion : comptée, la colonne ⚠
|
|
|
|
|
s'allumait sur toute installation, dès sa première ligne."""
|
|
|
|
|
import tempfile
|
|
|
|
|
|
|
|
|
|
with tempfile.NamedTemporaryFile(
|
|
|
|
|
"w", suffix=".log", delete=False, encoding="utf-8"
|
|
|
|
|
) as fh:
|
|
|
|
|
fh.write(
|
|
|
|
|
"Warning: Permanently added '10.0.0.9' (ED25519) to the"
|
|
|
|
|
" list of known hosts.\n__ERPLIBRE_EXIT__ 0\n"
|
|
|
|
|
)
|
|
|
|
|
chemin = fh.name
|
|
|
|
|
self.addCleanup(os.unlink, chemin)
|
|
|
|
|
self.assertEqual((0, 0), mon.scan_log_errors(chemin))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
unittest.main(verbosity=1)
|