La compilation butait sur « Too many zip entries 123678 (MAX=65535) » : un APK
est un ZIP, et le dépôt mobile verse 122 684 fichiers d'assets pour 337 qui
sont l'application. Le levier existe et il est documenté chez lui
(doc/SERVICES.md) : ERPLIBRE_MANIFEST_PATH, ici pointé sur un manifeste vide —
« ces dépôts-là : aucun ». Le plugin l'annonce, « 0 repos ».
Mesuré sur erplibre-ubuntu-2604-gnome : dist passe de 123 019 fichiers à 336,
l'APK sort à 59 Mo et 2 472 entrées, et la phase mobile entière rend 0, tests
Vitest compris — 75 fichiers, 1938 tests. Qui veut les dépôts pose la variable
lui-même : elle est respectée. Mesure d'attente, à retirer quand ils tiendront
sous le plafond du ZIP.
--- EN ---
The build hit "Too many zip entries 123678 (MAX=65535)": an APK is a ZIP, and
the mobile repo pours 122,684 asset files in for 337 that are the application.
The lever exists and that repo documents it (doc/SERVICES.md):
ERPLIBRE_MANIFEST_PATH, pointed here at an empty manifest — "those repos:
none". The plugin says so itself, "0 repos".
Measured on erplibre-ubuntu-2604-gnome: dist drops from 123,019 files to 336,
the APK comes out at 59 MB with 2,472 entries, and the whole mobile phase
returns 0, Vitest included — 75 files, 1938 tests. Set the variable yourself
and the repos come back. A stopgap, to drop once they fit under the ZIP
ceiling.
Assisted-by: Claude Opus 5
Sur erplibre-ubuntu-2604-gnome, l'APK s'est fait tuer par le noyau. La cause
est ici : « $! » désigne xvfb-run, un script, et le tuer n'atteint ni PyCharm
ni Xvfb — l'IDE tournait encore 45 minutes après son étape, avec 1,9 Go, quand
Gradle a demandé ses 6,8 Go sur 12. C'est le GROUPE qu'on tue maintenant, et
plus rien ne survit : mesuré, 2 Go rendus.
Le .idea n'était jamais écrit non plus : 122 684 des 123 021 fichiers d'assets
du dépôt mobile épuisaient les 65 536 watches inotify. Relevées à 524288, le
projet se crée. Restent 4 Go de swap avant de compiler, et un diagnostic qui
nomme la mémoire — avec le compte de l'oom-killer — puis la limite ZIP de
65 535 entrées, sur laquelle la compilation bute désormais, en amont.
--- EN ---
On erplibre-ubuntu-2604-gnome the APK was killed by the kernel. The cause is
here: "$!" is xvfb-run, a script, and killing it reaches neither PyCharm nor
Xvfb — the IDE was still running 45 minutes after its step, holding 1.9 GB,
when Gradle asked for its 6.8 GB out of 12. The GROUP is killed now, and
nothing survives it: 2 GB given back, measured.
The .idea was never written either: 122,684 of the mobile repo's 123,021 asset
files exhausted the 65,536 inotify watches. Raised to 524288, the project gets
created. Also 4 GB of swap before building, and a diagnostic naming memory —
with the oom-killer count — then the 65,535-entry ZIP limit the build now hits,
upstream of us.
Assisted-by: Claude Opus 5
Trois défauts vus en conduisant le menu sur de vraies VM. « setsid » détache,
donc son code de retour vaut 0 même quand rien ne se lance : le menu disait
« Démarré » sur une VM sans SDK, dont le journal disait « not found ». Le
démarrage attend maintenant de VOIR le processus, et à défaut cite le journal.
Une sonde préalable lit binaire et AVD d'un coup : une VM déployée sans cocher
l'outil est le cas normal, pas une panne. Vérifié sur deux VM réelles — outillée
(True), migration (« aucun binaire emulator »), sans rien y démarrer.
Et tout ce qui n'était pas « 2 » démarrait l'émulateur : un « n » de travers
suffisait. Au passage, « Choice » n'était traduit dans aucun des trois menus
qui l'affichent.
--- EN ---
Three defects found while driving the menu against real VMs. setsid detaches,
so its exit code is 0 even when nothing launches: the menu said "Started" on a
VM with no SDK, whose log said "not found". Starting now waits to SEE the
process, and quotes the log when it never appears.
A prior probe reads binary and AVD in one go: a VM deployed without ticking the
tool is the normal case, not a failure. Verified on two real VMs — tooled
(True), migration ("no emulator binary") — without starting anything there.
And anything that was not "2" started the emulator; a stray "n" was enough.
Along the way, "Choice" was untranslated in all three menus showing it.
Assisted-by: Claude Opus 5
Le choix 3 du menu tunnel levait « _qemu_console_tunnel() takes 1 positional
argument but 3 were given » : la fonction lit name et src sans les recevoir.
flake8 le disait déjà (F821 sur name et src), personne ne l'écoutait.
Ce menu n'avait aucun test, et c'est ce qui a laissé passer l'appel. Ses
quatre choix sont donc parcourus pour de vrai, jusqu'à la commande imprimée,
console comprise. Le défaut remis en place fait bien tomber les tests.
--- EN ---
Choice 3 of the tunnel menu raised "_qemu_console_tunnel() takes 1 positional
argument but 3 were given": the function reads name and src without taking
them. flake8 already said so (F821 on name and src); nobody listened.
That menu had no test at all, which is how the call shipped. All four of its
choices are now walked for real, down to the printed command, the console
included. Putting the defect back does make the tests fail.
Assisted-by: Claude Opus 5
Le tunnel n'était que du texte à recopier, et le démarrage restait manuel :
un second émulateur sur le même AVD tue le premier (« Running multiple
emulators »), rencontré deux fois. Le menu le détecte donc avant de lancer.
La fenêtre décide du reste. Sans elle, todo.py démarre à distance, détaché
par setsid dans le groupe kvm, et enchaîne sur scrcpy ; avec elle, la
commande doit partir du poste qui possède l'affichage, pas d'ici.
Mesuré : l'émulateur n'écoute que sur le 127.0.0.1 de la VM — l'hyperviseur
est refusé sur IP:5555 — d'où le -J qui met la VM en dernier saut. Vérifié
sur erplibre-mobile-proof : poignée de main adb CNXN rendant
sdk_gphone64_x86_64, enveloppe détachée survivant au ssh ; 30 tests.
--- EN ---
The tunnel was only text to copy, and starting the emulator stayed manual:
a second emulator on the same AVD kills the first ("Running multiple
emulators"), hit twice. The menu now detects it before starting.
The window decides the rest. Without one, todo.py starts it remotely,
detached by setsid in the kvm group, and chains into scrcpy; with one, the
command must run from the workstation that owns the display, not from here.
Measured: the emulator only listens on the VM's 127.0.0.1 — the hypervisor
is refused on IP:5555 — hence the -J putting the VM last. Verified on
erplibre-mobile-proof: an adb CNXN handshake returning sdk_gphone64_x86_64,
a detached wrapper outliving the ssh; 30 tests.
Assisted-by: Claude Opus 5
X11 carries raw pixels: 0.62 megapixel per frame even after shrinking the
screen, rendered in software. scrcpy receives an H.264 stream encoded BY the
device, so the emulator runs with no window at all — no X11 anywhere. The
tunnel menu gained a fourth kind, reusing the target list it already reads from
~/.ssh/config, ProxyJump included: the hard part was already solved there.
It forwards 5555, the emulator's own adb port, not 5037: tunnelling the adb
server would force the user to kill the one on their workstation, which holds
the same port. Verified through the tunnel from the hypervisor: an adb CNXN
handshake answers device::ro.product.name=sdk_gphone64_x86, exactly what adb
connect does. ss -ltn in the VM confirms 5554, 5555 and 5037 all listen on
127.0.0.1.
--- FR ---
X11 transporte des pixels bruts : 0,62 mégapixel par image même après réduction
de l'écran, en rendu logiciel. scrcpy reçoit un flux H.264 encodé PAR
l'appareil, si bien que l'émulateur tourne sans aucune fenêtre — plus de X11
nulle part. Le menu de tunnel gagne un quatrième type, qui réutilise la liste de
cibles qu'il lit déjà dans ~/.ssh/config, ProxyJump compris : le plus dur y
était déjà résolu.
Il redirige 5555, le port adb de l'émulateur lui-même, et non 5037 : tunneliser
le serveur adb obligerait à tuer celui du poste, qui occupe le même port.
Vérifié à travers le tunnel depuis l'hyperviseur : une poignée de main adb CNXN
répond device::ro.product.name=sdk_gphone64_x86, ce que fait exactement adb
connect. « ss -ltn » dans la VM confirme que 5554, 5555 et 5037 écoutent tous
sur 127.0.0.1.
Assisted-by: Claude Opus 5
Reported from a workstation: "Selected GPU option 'swiftshader_indirect' is
not valid, switching to auto", then "Your GPU drivers may have a bug". That
mode no longer exists in emulator 37.1; it fell back to swangle by itself, so
it worked while printing two errors that read like a fault. emulator -help-gpu
lists exactly four modes: auto, host, swiftshader, swangle. The AVD now asks
for swangle. Checked on the VM: zero GPU errors where there were two,
vulkan:swiftshader gles:swangle selected outright, boot in 55 s.
--- FR ---
Remonté depuis un poste : « Selected GPU option 'swiftshader_indirect' is not
valid, switching to auto », puis « Your GPU drivers may have a bug ». Ce mode
n'existe plus dans l'émulateur 37.1 ; il retombait de lui-même sur swangle,
donc cela fonctionnait en affichant deux erreurs qui se lisent comme une panne.
« emulator -help-gpu » énumère exactement quatre modes : auto, host,
swiftshader, swangle. L'AVD demande désormais swangle. Vérifié sur la VM : zéro
erreur GPU là où il y en avait deux, vulkan:swiftshader gles:swangle choisis
d'emblée, boot en 55 s.
Assisted-by: Claude Opus 5
"It launches but it is too slow." The Pixel profile gives 1080x2400 — 2.6
megapixels pushed frame by frame through SSH, in software rendering. The AVD
now declares 540x1140 at 240 dpi: 0.62 megapixel, 4.2 times less. Measured on
the VM after the change: wm size reports 540x1140, and a full-screen capture
drops from 220 KB to 49 KB. Android handles the density and the app does not
notice; whoever wants the real size overrides it with -skin 1080x2400.
The printed command also gained -XC over -X, X11 compression being free on a
remote display, and -no-boot-anim. Checked: ca.erplibre.home relaunched at the
new size, MainActivity resumed.
--- FR ---
« Ça se lance mais c'est trop lent. » Le profil Pixel donne 1080x2400, soit
2,6 mégapixels poussés image par image dans SSH, en rendu logiciel. L'AVD
déclare maintenant 540x1140 à 240 ppp : 0,62 mégapixel, 4,2 fois moins. Mesuré
sur la VM après le changement : « wm size » rend 540x1140, et une capture plein
écran tombe de 220 Ko à 49 Ko. Android gère la densité et l'application ne s'en
aperçoit pas ; qui veut la taille réelle l'écrase par « -skin 1080x2400 ».
La commande affichée gagne aussi « -XC » au lieu de « -X », la compression X11
étant gratuite sur un écran distant, et « -no-boot-anim ». Vérifié :
ca.erplibre.home relancé à la nouvelle taille, MainActivity au premier plan.
Assisted-by: Claude Opus 5
The app runs in the emulator now, and two of my own checks were wrong. With an
injected ABI, AGP writes to intermediates/apk/debug, not outputs/apk/debug: a
SUCCESSFUL build was reported "no APK produced" because only the second path
was searched. That same mode marks the APK testOnly, so adb refuses it without
-t — INSTALL_FAILED_TEST_ONLY. Both measured on the emulator, where
ca.erplibre.home then installed, launched, and ran its SQLite migrations.
The manifest also stops tracking sentencepiece at master, which made the build
non-reproducible and, since August, unbuildable: master fetches protobuf and
compiles protoc for the Android target before running it on the host. v0.2.1
ships its .pb.cc pre-generated. Its own absl subset still lacks
flags/flag.cc, so that library remains to be sorted out upstream.
--- FR ---
L'application tourne dans l'émulateur, et deux de mes propres contrôles étaient
faux. Avec une ABI injectée, AGP écrit dans intermediates/apk/debug et non dans
outputs/apk/debug : une compilation RÉUSSIE était rapportée « aucun APK
produit » parce que je ne regardais que le second chemin. Ce même mode marque
l'APK testOnly, et adb le refuse sans -t — INSTALL_FAILED_TEST_ONLY. Les deux
mesurés sur l'émulateur, où ca.erplibre.home s'est ensuite installé, lancé, et
a joué ses migrations SQLite.
Le manifeste cesse aussi de suivre sentencepiece à master, ce qui rendait la
compilation non reproductible et, depuis août, impossible : master récupère
protobuf et compile protoc pour la cible Android avant de l'exécuter sur
l'hôte. La v0.2.1 livre ses .pb.cc pré-générés. Son propre sous-ensemble absl
manque encore flags/flag.cc : cette bibliothèque reste à régler en amont.
Assisted-by: Claude Opus 5
Reported from a workstation: "emulator: command not found". The line the
emulator step printed relied on PATH, and "ssh host 'command'" reads neither
~/.profile nor ~/.bashrc — Ubuntu even opens the latter with a return for
non-interactive shells. So what install-android.sh writes there never applies
to those commands. Both printed lines now carry absolute paths.
Behind it, a second one: the emulator ships TWO qemu binaries and only the
headless one does without PulseAudio. The windowed one — the ssh -X case —
links libpulse.so.0, absent from cloud images, and stops on "cannot open
shared object file" even with -no-audio. Measured: it is the only missing
library, the Qt dependencies travel in the bundle. Checked after the fix: the
windowed binary starts and reports its version.
--- FR ---
Remonté depuis un poste : « emulator: command not found ». La ligne affichée
par l'étape émulateur comptait sur le PATH, or « ssh hôte 'commande' » ne lit
ni ~/.profile ni ~/.bashrc — Ubuntu ouvre même le second par un return pour
les shells non interactifs. Ce que install-android.sh y écrit ne s'applique
donc jamais à ces commandes. Les deux lignes affichées portent maintenant des
chemins absolus.
Derrière, une seconde : l'émulateur livre DEUX binaires qemu et seul le
headless se passe de PulseAudio. Celui qui ouvre une fenêtre — le cas du
ssh -X — lie libpulse.so.0, absente des images cloud, et s'arrête sur
« cannot open shared object file » même avec -no-audio. Mesuré : c'est la
seule bibliothèque qui manque, les dépendances Qt voyagent dans le bundle.
Vérifié après correction : le binaire fenêtré démarre et donne sa version.
Assisted-by: Claude Opus 5
Running it on a VM was the only way to find these. install_os never installed
python3-venv, so .venv.erplibre was born crippled — bin/python but no pip, no
activate — and everything downstream failed on "No module named git"; one line
of dependency fixes it. Capacitor 8 needs a JDK 21 where the mobile
repository's installer puts 17, and Gradle must RUN on it. That installer is
not idempotent either, so it is replayed only when something is missing.
The new emulator option creates an AVD from the SDK's own device list — the
newest plain Pixel, smallest screen — with software rendering written into its
config so ssh -X does not open a black screen, and adds the user to the kvm
group, without which it refuses to start. Checked on a VM: boot completed in
10 s, adb sees emulator-5554, Android 16 x86_64, no KVM refusal. Vitest: 1938
tests pass. The APK still fails, upstream: sentencepiece builds protoc for the
target then runs it on the host.
--- FR ---
Seule l'exécution sur une VM pouvait trouver ceci. install_os n'installait pas
python3-venv, si bien que .venv.erplibre naissait infirme — bin/python mais ni
pip ni activate — et tout ce qui en dépend tombait sur « No module named
git » ; une ligne de dépendance suffit. Capacitor 8 réclame un JDK 21 là où
l'installateur du dépôt mobile pose un 17, et Gradle doit TOURNER dessus. Cet
installateur n'est pas idempotent non plus : il n'est rejoué que s'il manque
quelque chose.
La nouvelle option crée un AVD depuis la liste de profils du SDK — le Pixel
simple le plus récent, plus petit écran —, écrit le rendu logiciel dans sa
configuration pour qu'ssh -X n'ouvre pas un écran noir, et ajoute
l'utilisateur au groupe kvm, sans quoi il refuse de démarrer. Vérifié sur une
VM : boot en 10 s, adb voit emulator-5554, Android 16 x86_64, aucun refus de
KVM. Vitest : 1938 tests passent. L'APK échoue encore, en amont :
sentencepiece bâtit protoc pour la cible puis l'exécute sur l'hôte.
Assisted-by: Claude Opus 5
install-android.sh writes its exports into ~/.bashrc, which a GNOME session
never reads: Android Studio launched from the menu saw no ANDROID_HOME and
offered to download a second SDK, next to the one the mobile build had just
installed. environment.d is the channel the user session actually reads, so
both point at $HOME/android. An empty ~/.android/repositories.cfg is created
too — without it the first-run wizard stops on an error instead of offering
anything.
--- FR ---
install-android.sh écrit ses exports dans ~/.bashrc, qu'une session GNOME ne
lit jamais : Android Studio lancé depuis le menu ne voyait aucun ANDROID_HOME
et proposait de télécharger un second SDK, à côté de celui que la compilation
mobile venait de poser. environment.d est le canal que la session utilisateur
lit vraiment, et les deux pointent désormais $HOME/android. Un
~/.android/repositories.cfg vide est créé au passage : sans lui, l'assistant
de première ouverture s'arrête sur une erreur au lieu de proposer quoi que ce
soit.
Assisted-by: Claude Opus 5
A VM was declared ready without anything proving it. The mobile option now
adds the repository to the manifest (additive, so it rides along an Odoo 18
install), runs the mobile repository's own install-android.sh — licences
accepted included — then npm ci, vite build, cap sync, gradlew assembleDebug
and npm test. A failure fails the VM: the exit code reaches the dashboard.
Two gaps in that upstream installer had to be filled: unzip and wget, which
no cloud image ships, and the SDK platform — it installs android-34 while
variables.gradle asks for compileSdk 36, so the number is read from the file
rather than frozen. Gradle writes tens of megabytes and hundreds of harmless
lines carrying the word error: that output goes to a file of its own, and the
log gets the named cause instead.
--- FR ---
Une VM était déclarée prête sans que rien ne le prouve. L'option mobile ajoute
maintenant le dépôt au manifeste (additif, donc il accompagne une installation
Odoo 18), lance l'install-android.sh du dépôt mobile lui-même — licences
acceptées comprises —, puis npm ci, vite build, cap sync, gradlew
assembleDebug et npm test. Un échec fait échouer la VM : le code de sortie
remonte au tableau de bord.
Deux manques de cet installateur amont ont dû être comblés : unzip et wget,
qu'aucune image cloud ne livre, et la plateforme SDK — il pose android-34
quand variables.gradle réclame compileSdk 36, d'où le chiffre lu dans le
fichier plutôt que figé. Gradle écrit des dizaines de mégaoctets et des
centaines de lignes anodines portant le mot error : cette sortie part dans un
fichier à part, et le journal reçoit la cause nommée.
Assisted-by: Claude Opus 5
Two measurements, one after the other. The unified PyCharm that
code=PCC&latest now serves stops on its licence: the log says
NoValidIdeLicense then "Get licenses: request requires authentication", and
no project is ever opened — so no .idea, so nothing for the install to
configure. The Community line asks for no account and is still patched
(2025.2.6.2 on 2026-07-29); it is resolved from the release feed, so no
version is frozen here.
That build then froze 1.3 s after startup: the trust dialog, invisible under
Xvfb and waiting for a click. idea.trust.all.projects unblocks it. Checked
on an Ubuntu 26.04 VM: .idea complete in 195 s, and pycharm_configuration.py
writes its exclusions into erplibre.iml.
--- FR ---
Deux mesures, l'une après l'autre. Le PyCharm unifié que sert désormais
code=PCC&latest s'arrête sur sa licence : le journal dit NoValidIdeLicense
puis « Get licenses: request requires authentication », et aucun projet ne
s'ouvre — donc pas de .idea, donc rien à configurer pour l'installation. La
ligne Community ne demande aucun compte et reste corrigée (2025.2.6.2 le
2026-07-29) ; elle est résolue depuis le flux des versions, sans qu'aucun
numéro ne soit figé ici.
Ce build se figeait ensuite 1,3 s après le démarrage : la fenêtre de
confiance, invisible sous Xvfb et attendant un clic.
idea.trust.all.projects la lève. Vérifié sur une VM Ubuntu 26.04 : .idea
complet en 195 s, et pycharm_configuration.py écrit ses exclusions dans
erplibre.iml.
Assisted-by: Claude Opus 5
A graphical VM was a desktop and nothing else: every developer tool had to
be installed by hand afterwards. A check list now carries them, filtered
per machine — Android Studio is x86_64 only, Google publishes no Linux
aarch64 build — and their disk cost reaches the plan before any qcow2 is
created. They are installed BEFORE the clone: PyCharm writes the .idea/ of
the repository, and the install that follows is what runs
pycharm_configuration.py, through update_env_version.pycharm_update().
Its launcher is named studio, which is enough to conclude the install
failed; it now answers to android-studio too. GNOME extensions come from
the site by UUID, per running Shell: the same endpoint serves gTile v59
for GNOME 46 and v62 for 48. Checked with stubs: every tool can fail and
the install's exit code still wins, 45 tests.
--- FR ---
Une VM graphique n'était qu'un bureau : chaque outil de développement
restait à poser à la main. Une liste à cocher les porte, filtrés machine
par machine — Android Studio n'existe qu'en x86_64, Google ne publiant
aucune archive Linux aarch64 — et leur place disque atteint le plan avant
qu'un seul qcow2 ne soit créé. Ils sont posés AVANT le clone : PyCharm
écrit le .idea/ du dépôt, et c'est l'installation qui suit qui lance
pycharm_configuration.py, via update_env_version.pycharm_update().
Son lanceur s'appelle studio, ce qui suffit à conclure à un échec ; il
répond désormais aussi à android-studio. Les extensions GNOME viennent du
site par UUID, selon le Shell qui tourne : le même point d'entrée sert
gTile v59 pour GNOME 46 et v62 pour 48. Vérifié avec des leurres : chaque
outil peut échouer sans que le code de sortie de l'installation ne change,
45 tests.
Assisted-by: Claude Opus 5
L'ecran de deploiement portait sa PROPRE copie de S390X_DISTROS, sous un
commentaire promettant qu'elle restait « coherente avec deploy_qemu.py ».
Une copie ne tient aucune promesse : Debian a gagne s390x la-bas et
l'ecran ne le proposait toujours pas.
_qemu_arch_distros lit desormais ARCH_DISTRO_SUPPORT, et _qemu_arches_for
y passe aussi pour que « toutes les architectures » n'offre rien que
deploy_qemu refuserait ensuite. Les tuples locaux restent en repli si
l'import echoue. L'import est memorise : le catalogue l'interrogeait une
fois par couple (distro, version).
Plancher memoire de 2048 Mio sur la voie installateur, annonce et jamais
abaissant : d-i deplie un systeme de fichiers en RAM la ou une image
cloud arrive installee, et le manque s'y voit comme un ecran fige.
--- EN ---
The deploy screen carried its OWN copy of S390X_DISTROS, under a comment
promising it stayed "consistent with deploy_qemu.py". A copy keeps no
promise: Debian gained s390x over there and the screen still did not
offer it.
_qemu_arch_distros now reads ARCH_DISTRO_SUPPORT, and _qemu_arches_for
goes through it too, so "all architectures" offers nothing deploy_qemu
would later refuse. The local tuples remain as a fallback if the import
fails. The import is memoised: the catalogue queried it once per
(distro, version) pair.
A 2048 MiB memory floor on the installer path, announced and never
lowering: d-i unpacks a filesystem into RAM where a cloud image arrives
installed, and running short shows up as a frozen screen.
Assisted-by: Claude Opus 5
(cherry picked from commit f0ee70c1ac43ba2bc9f5fb78fe71f821a15daf7d)
Le choix [3] listait les domaines libvirt de la machine qui execute
todo.py. Or on pilote un parc imbrique depuis l'exterieur : cette
machine ne connait pas les VM d'un orchestrateur, et la bonne cible
n'apparaissait donc jamais dans la liste.
L'ecran VNC appartient a QEMU, donc a l'hyperviseur. Tunneler vers
l'invite ne trouve rien : le socket n'existe pas de ce cote. La cible
est le ProxyJump declare pour l'hote, lu par « ssh -G » — la seule
lecture qui couvre Match, wildcards et Include. Le nom compose
« saut+vm » n'est qu'un libelle.
Le port est lu sur cet hyperviseur, sans sudo d'abord : le groupe
libvirt suffit souvent, et « sudo -n » distant echoue faute de TTY.
--- EN ---
Choice [3] listed the libvirt domains of the machine running todo.py.
But a nested fleet is driven from outside: that machine knows nothing
of an orchestrator's VMs, so the right target never showed up.
The VNC screen belongs to QEMU, hence to the hypervisor. Tunnelling to
the guest finds nothing: the socket does not exist on that side. The
target is the ProxyJump declared for the host, read via "ssh -G" — the
only reading that covers Match, wildcards and Include. The composite
"jump+vm" name is just a label.
The port is read on that hypervisor, without sudo first: libvirt group
membership often suffices, and remote "sudo -n" fails for lack of a TTY.
Assisted-by: Claude Opus 5
(cherry picked from commit 2fb846d502cc8026732b58e1f487d7a6726f17eb)
A migration leaves one database per step and they all still exist, so the
tool compares them side by side rather than replaying anything. Six Odoo
starts would cost an hour, write to the databases and need the checkout
switched at each step; the same inspection in SQL takes under half a
second per database and touches nothing. Measured on a real migration:
seven databases surveyed in 3.8 seconds.
It reports what each step gained and lost — modules, models, views, menus,
attachments — and ends on the comparison of the ENDS, which is not the sum
of the steps: a module dropped at 15 and put back at 17 lost nothing, and
adding the steps would count it twice.
The signal that matters is rows. A module removed is visible; a table
going from four thousand rows to zero is visible nowhere.
Two rename heuristics were tried and rejected on real data before the one
that holds. Row count alone paired an account tag table with dms_directory
— both had seven rows. A shared five-letter word paired two cleanup
tables. Overall name similarity separates them. And a renamed table stays
in the loss list, annotated: removing it was the real danger, since a
wrong pairing would have hidden a genuine loss.
--- FR ---
[ADD] analyse : ce qu'une migration a gagné et perdu, palier par palier
Une migration laisse une base par palier et elles existent toutes encore :
l'outil les compare côte à côte plutôt que de rejouer quoi que ce soit.
Six démarrages d'Odoo coûteraient une heure et écriraient dans les bases ;
la même inspection en SQL prend moins d'une demi-seconde par base. Mesuré
sur une vraie migration : sept bases inspectées en 3,8 secondes.
Le bilan final compare les EXTRÉMITÉS, ce qui n'est pas la somme des
paliers : un module retiré en 15 puis remis en 17 n'a rien perdu.
Le signal qui compte, ce sont les lignes. Un module en moins se voit ; une
table qui passe de quatre mille lignes à zéro ne se voit nulle part.
Deux rapprochements de renommage ont été essayés et rejetés sur des
données réelles. Et une table renommée reste dans la liste des pertes,
annotée : l'en retirer était le vrai danger.
Assisted-by: Claude Opus 5
Realising at a prompt that an earlier step deserved another answer had one
way out: Ctrl+C. That leaves the progression as it stands and makes you find
the resume screen again to rewind. « b » does it properly — it shows the
steps, rewinds the state, writes it, and says what to relaunch.
Cancelling that must not stop the migration, which is the trap: it returns to
the same prompt, exactly where you were. Removing that guard makes two tests
fail.
The COW warning printed « -d DB -t odooXX.0 » for commands meant to be pasted.
It knows both values, so it prints them, and offers --shape as well since that
is half the answer.
Checked on the four outcomes: a normal answer passes through, « b » then a
step rewinds and stops, cancelling and an unknown step both continue. 15 tests.
--- FR ---
S'apercevoir à une invite qu'une étape antérieure méritait un autre choix
n'avait qu'une issue : Ctrl+C. Cela laisse la progression telle quelle et
oblige à retrouver l'écran de reprise pour rembobiner. « b » le fait
proprement — il montre les étapes, rembobine l'état, l'écrit, et dit quoi
relancer.
Y renoncer ne doit pas arrêter la migration, et c'est le piège : on revient à
la même invite, exactement là où l'on était. Retirer ce garde-fou fait tomber
deux tests.
L'avertissement COW affichait « -d DB -t odooXX.0 » pour des commandes faites
pour être collées. Il connaît les deux valeurs, donc il les écrit, et propose
aussi --shape puisque c'est la moitié de la réponse.
Vérifié sur les quatre issues : une réponse normale passe, « b » puis une
étape rembobine et arrête, annuler et un choix inconnu continuent. 15 tests.
Assisted-by: Claude Opus 5
The gap between 4 and 8 GB was the first one worth filling -- it is the
usual size of an ERPLibre development VM -- but every neighbouring gap
had the same fault: the lists jumped precisely where development VMs
live.
RAM now offers every gigabyte up to 16, and vCPU every count up to 16.
Disk gains 30, 50, 100 and 160 G, the steps missing between those already
there. Above 16 nothing changes: those jumps are deliberate, a machine
that large not being sized by picking from a list.
A typed value was already accepted in both interfaces, so these are a
convenience, never a constraint.
--- FR ---
L'écart entre 4 et 8 Go était le premier à combler — c'est la taille
courante d'une VM de développement ERPLibre — mais tous les écarts
voisins avaient le même défaut : les listes sautaient précisément là où
vivent les VM de développement.
La RAM offre désormais chaque gigaoctet jusqu'à 16, et les vCPU chaque
valeur jusqu'à 16. Le disque gagne 30, 50, 100 et 160 G, les paliers qui
manquaient entre ceux déjà présents. Au-delà de 16, rien ne change : ces
sauts sont voulus, une machine de cette taille ne se dimensionne pas en
piochant dans une liste.
Une valeur tapée était déjà acceptée dans les deux interfaces : ce sont
donc des commodités, jamais une contrainte.
Assisted-by: Claude Opus 5
A "Remote desktop tunnel" entry under SSH configuration. It lists the
targets, resolves them, and composes the full command -- nothing to fill
in.
todo.py runs on the libvirt HOST; the tunnel starts from the workstation.
It cannot open it, but it alone knows the VM's private IP, its port and
the address through which it was reached. That last one comes from
SSH_CONNECTION, whose third field is exactly the server address used --
far safer than a "hostname" that may resolve to nothing outside.
When ~/.ssh/config already carries the entry, the tunnel takes it:
"ssh -N -L 5902:localhost:5901 <vm>". ProxyJump makes the route and
"localhost" means the VM's OWN loopback, so the tunnel survives an IP
change. That config is also the right source of targets, not the local
libvirt: a graphical VM is often nested, and virsh would only ever list
the orchestrator.
Two details: the hypervisor console needs VNC bound to the loopback, as
"listen=none" opens no socket at all; and two menu entries had no icon.
--- FR ---
Une entrée « Tunnel bureau distant » sous Configuration SSH. Elle liste
les cibles, les résout, et compose la commande complète — rien à remplir.
todo.py tourne sur l'HÔTE libvirt ; le tunnel, lui, part du poste de
travail. Il ne peut donc pas l'ouvrir, mais il est le seul à connaître
l'IP privée de la VM, son port et l'adresse par laquelle on l'a joint.
Cette dernière vient de SSH_CONNECTION, dont le troisième champ est
exactement l'adresse serveur utilisée — bien plus sûr qu'un « hostname »
qui peut ne rien résoudre depuis l'extérieur.
Quand ~/.ssh/config porte déjà l'entrée, le tunnel l'emprunte :
« ssh -N -L 5902:localhost:5901 <vm> ». Le ProxyJump fait la route et
« localhost » désigne le bouclage DE LA VM, si bien que le tunnel survit à
un changement d'IP. Ce fichier est aussi la bonne source de cibles, pas le
libvirt local : une VM graphique est souvent imbriquée, et virsh ne
listerait jamais que l'orchestrateur.
Deux détails : la console de l'hyperviseur exige un VNC sur la boucle
locale, « listen=none » n'ouvrant aucun socket ; et deux entrées de menu
n'avaient pas d'icône.
Assisted-by: Claude Opus 5
The VM announced "Remote desktop: RDP 3389" without saying how to get
there. Its address is on libvirt's private network, which is not routed
from outside its host: the port alone helps nobody.
It now prints the tunnel to set up, address already filled in --
"ssh -L 3390:<ip>:3389 <user>@<libvirt-host>", then an RDP client on
localhost:3390. Likewise in VNC for Arch, on 5902.
This is the only workable route when the libvirt host is itself a VM
with no graphical interface: the SPICE console is on "listen=none" and
assumes virt-viewer ON that host.
--- FR ---
La VM annonçait « Bureau distant : RDP 3389 » sans dire par où passer.
Son adresse est sur le réseau privé de libvirt, qui n'est pas routé
depuis l'extérieur de son hôte : le port seul n'aide personne.
Elle imprime maintenant le tunnel à monter, adresse déjà remplie —
« ssh -L 3390:<ip>:3389 <user>@<hote-libvirt> », puis un client RDP sur
localhost:3390. Idem en VNC pour Arch, sur 5902.
C'est le seul chemin praticable quand l'hôte libvirt est lui-même une VM
sans interface graphique : la console SPICE est en « listen=none » et
suppose virt-viewer SUR cet hôte.
Assisted-by: Claude Opus 5
The timezone was typed by hand. A misspelt IANA name is not rejected by
cloud-init: it is IGNORED. The VM stays on UTC, and you only notice from
the timestamps, once deployed.
A list of twenty-five zones, Québec first, then the rest of Canada and
the places one actually meets. The host's zone goes to the top, without
duplication: a machine outside this list must still see its own at a
glance.
NAMES, not offsets: "UTC-5" says nothing about daylight saving and
cloud-init will not take it. A name carries its own switching rules.
"free value…" keeps the door open to the other six hundred zones in the
database. The choice is copied into the field, which stays the only
value the spec reads -- one place holds the answer.
--- FR ---
Le fuseau se tapait à la main. Un nom IANA mal orthographié n'est pas
refusé par cloud-init : il est IGNORÉ. La VM reste en UTC, et on ne s'en
aperçoit qu'aux horodatages, une fois déployée.
Une liste de vingt-cinq fuseaux, le Québec d'abord, puis le reste du
Canada et les places qu'on rencontre en pratique. Le fuseau de l'hôte
passe en tête, sans doublon : une machine hors de cette liste doit voir
le sien en un coup d'œil.
Des NOMS, pas des décalages : « UTC-5 » ne dit rien de l'heure d'été et
cloud-init n'en veut pas. Un nom porte ses propres règles de bascule.
« libre… » garde la porte ouverte aux six cents autres fuseaux de la
base. Le choix est recopié dans le champ, qui reste la seule valeur lue
par la spec — un seul endroit porte la réponse.
Assisted-by: Claude Opus 5
A profile list per row, beside the branch: "ERPLibre + Odoo 18",
"Odoo 17", "ERPLibre alone". The form's profile stays the default; picking
another for a VM creates an override, going back clears it.
It reaches down to execution, like the branch and the type before it.
"final_cmd" now takes either a string for the whole fleet or a
{name: command} map, and the remote command is built per machine as soon
as profiles differ. A VM can install Odoo 18 while another validates
Odoo 17, in the same deployment.
Both lists are widened -- branch to 24 columns, profile to 28. Truncated,
"develop" and "ERPLibre + Odoo 18" no longer showed what had been picked,
and that is precisely what one wants to re-read before deploying.
Two more defects: the global branch and Odoo choices reached nothing at
all, and the summary announced the global profile for every VM, then named
a version nothing would install.
--- FR ---
Une liste de profils par rangée, à côté de la branche : « ERPLibre +
Odoo 18 », « Odoo 17 », « ERPLibre seul ». Le profil du formulaire reste
le défaut ; en choisir un autre pour une VM crée une surcharge, y revenir
l'efface.
Il descend jusqu'à l'exécution, comme la branche et le type avant lui.
« final_cmd » accepte maintenant une chaîne pour tout le parc ou une carte
{nom: commande}, et la commande distante est bâtie par machine dès que les
profils diffèrent. Une VM peut donc installer Odoo 18 pendant qu'une autre
valide Odoo 17, dans le même déploiement.
Les deux listes sont élargies : la branche passe à 24 colonnes, le profil
à 28. Tronqués, « develop » et « ERPLibre + Odoo 18 » ne laissaient plus
voir ce qu'on avait choisi — et c'est précisément ce qu'on veut relire
avant de déployer.
Deux autres défauts : les choix globaux de branche et d'Odoo n'atteignaient
rien, et le sommaire annonçait le profil global pour toutes les VM, puis
nommait une version que rien n'installait.
Assisted-by: Claude Opus 5
A branch list per row. The form's branch stays the default; picking
another for a VM creates an override, going back clears it -- the VM then
follows the form again, which is what one expects when restoring the
common choice.
It reaches all the way down to execution, like VM type before it.
"branch" now takes either a string for the whole fleet or a {name: branch}
map, and the remote command is built per machine as soon as branches
differ. The ground was ready: launch_installs already knows how to take
one command per VM.
A VM can therefore carry develop while another validates 1.6.0, in the
same deployment and the same monitoring table.
One trap: a row's branch fell back to develop on remount, the list being
rebuilt from the form rather than from the override it already held.
--- FR ---
Une liste de branches par rangée. La branche du formulaire reste le
défaut ; en choisir une autre pour une VM crée une surcharge, y revenir
l'efface — la VM suit alors de nouveau le formulaire, ce qui est le plus
attendu quand on remet le choix commun.
Elle descend jusqu'à l'exécution, comme le type de VM avant elle.
« branch » accepte désormais une chaîne pour tout le parc ou une carte
{nom: branche}, et la commande distante est bâtie par machine dès que les
branches diffèrent. Le terrain était prêt : launch_installs sait déjà
prendre une commande par VM.
Une VM peut donc porter develop pendant qu'une autre valide 1.6.0, sur le
même déploiement et dans le même tableau de suivi.
Un piège : la branche d'une rangée retombait sur develop au remontage, la
liste étant rebâtie depuis le formulaire plutôt que depuis la surcharge
qu'elle portait déjà.
Assisted-by: Claude Opus 5
erplibre-ubuntu-2404-gnome, erplibre-ubuntu-2404-mint. A graphical VM is
recognisable from a "virsh list", and its hostname says so too --
deploy_qemu uses the name as hostname when --hostname is not given.
This is not only cosmetic. The name is the COLLISION KEY: a graphical VM
and its server twin carried the same one, so the second was reported as
"already exists" and silently skipped. They are now distinct.
The suffix is applied AFTER overrides, the only point where each
machine's type is known now that it is chosen VM by VM. In the form the
name therefore follows the choice live, both ways.
"mint" rather than "cinnamon": that is the name chosen for the fleet,
the installed package still being Cinnamon from the distribution's own
repositories. The suffix lives with the flavour, in _QEMU_DESKTOP, and
the CLI calls the same function as the TUI rather than writing a second.
--- FR ---
erplibre-ubuntu-2404-gnome, erplibre-ubuntu-2404-mint. Une VM graphique
se reconnaît d'un « virsh list », et son nom d'hôte le dit aussi —
deploy_qemu prend le nom pour hôte quand --hostname n'est pas donné.
Ce n'est pas que cosmétique. Le nom sert de CLÉ DE COLLISION : une VM
graphique et sa jumelle serveur portaient le même, donc la seconde était
signalée « existe déjà » et silencieusement ignorée. Elles se
distinguent maintenant.
Le suffixe est appliqué APRÈS les surcharges, seul moment où le type de
chaque machine est connu depuis qu'il se choisit VM par VM. Dans le
formulaire, le nom suit donc le choix en direct, dans les deux sens.
« mint » et non « cinnamon » : c'est le nom retenu pour le parc, le
paquet installé restant Cinnamon depuis les dépôts de la distribution.
Le suffixe vit avec la saveur, dans _QEMU_DESKTOP, et la CLI appelle la
même fonction que la TUI plutôt que d'en écrire une seconde.
Assisted-by: Claude Opus 5
snap was not a choice but a fate: we disabled snapd, then gnome-core
pulled a Firefox snap that froze the install for thirty minutes. The
previous fix imposed "deb". Three answers are now offered.
deb nothing but .deb, epiphany-browser as the browser. Default,
and lightest: nothing extra to download.
flatpak Flatpak tooling as well, WITHOUT the Flathub remote or any
install. The machine is ready, the admin picks its remotes.
snap Ubuntu's default, snapd left running and Firefox as a snap.
It is the only mode where snapd is not disabled -- disabling
it was precisely the cause of the freeze.
The question is only asked when it means something: at least one
graphical VM on a distro shipping snapd, Ubuntu alone here. A server
pulls no snap, and Debian ships none. The TUI greys the choice out and
says why; the CLI does not ask.
Verified on a 26.04 VM: snapd is indeed preinstalled (2.75.2), flatpak
and its GNOME Software plugin are packaged, and firefox-esr does not
exist on Ubuntu.
--- FR ---
snap n'était plus un choix mais une fatalité : on coupait snapd, puis
gnome-core tirait un Firefox-snap qui figeait l'installation trente
minutes. Le correctif précédent imposait « deb ». Trois réponses sont
maintenant offertes.
deb rien que des .deb, epiphany-browser comme navigateur. Défaut,
et le plus léger : rien de plus à télécharger.
flatpak l'outillage Flatpak en plus, SANS dépôt Flathub ni
installation. La machine est prête, l'administrateur choisit
ses dépôts.
snap le défaut d'Ubuntu, snapd laissé actif et Firefox en snap.
C'est le seul mode où snapd n'est pas coupé — l'y couper
était précisément la cause du blocage.
La question n'est posée que lorsqu'elle a un sens : au moins une VM
graphique sur une distribution qui livre snapd, Ubuntu seule ici. Un
serveur ne tire aucun snap, et Debian n'en livre pas. La TUI grise le
choix et dit pourquoi ; la CLI ne le demande pas.
Vérifié sur une VM 26.04 : snapd y est bien préinstallé (2.75.2),
flatpak et son greffon GNOME Software sont empaquetés, et firefox-esr
n'existe pas sur Ubuntu.
Assisted-by: Claude Opus 5
A graphical Ubuntu VM stopped making progress, with no error, on "Unable
to contact the store, trying every minute for the next 30 minutes".
Nothing in the log says why, and the install just looks stuck.
The cause is ours. We disable snapd a few lines earlier, to stop its
refreshes during the install. Then gnome-core recommends "firefox", which
on Ubuntu is only a transitional package whose postinst runs "snap
install" -- against a snapd we just stopped.
It is a RECOMMENDS, with alternatives. So we exclude the snap packages and
name epiphany-browser, a real .deb. Excluding firefox alone was not
enough: apt fell back to chromium-browser, a transitional package too, and
Cinnamon froze the same way on thunderbird. Measured on the VM: with this
list, GNOME (844 packages) and Cinnamon (1167) pull none, without an apt
error.
--- FR ---
Une VM Ubuntu graphique cessait d'avancer, sans erreur, sur « Unable to
contact the store, trying every minute for the next 30 minutes ». Rien
dans le log n'en dit la raison, et l'installation paraît simplement figée.
La cause est nôtre. On coupe snapd quelques lignes plus haut, pour
empêcher ses rafraîchissements pendant l'installation. Puis gnome-core
recommande « firefox », qui n'est plus sur Ubuntu qu'un paquet de
transition dont le postinst lance « snap install » — contre un snapd qu'on
vient d'arrêter.
C'est un RECOMMENDS, avec alternatives. On écarte donc les paquets snap et
on nomme epiphany-browser, un vrai .deb. Écarter le seul firefox ne
suffisait pas : apt retombait sur chromium-browser, paquet de transition
lui aussi, et Cinnamon figeait pareillement sur thunderbird. Mesuré sur la
VM : avec cette liste, GNOME (844 paquets) et Cinnamon (1167) n'en tirent
aucun, sans erreur apt.
Assisted-by: Claude Opus 5
VM type was global: the whole fleet as servers, or all of them GNOME. It
now lives on the VM, all the way down -- the creation flag, and one remote
command per machine at install time, where a single one served them all.
The right pane is no longer a table but a row of widgets per VM: vCPU,
RAM, disk and type, each with its usual values and a free entry. The
left-hand fields become the shared default, which the screen now says. The
scope selector and the F2 modal go away: given two ways to do the same
thing, keep the visible one.
Three traps came out of it, and the tests lock them. Widget ids carry a
RANK, and the rank shifts when an entry is ticked, so an event from an
already destroyed widget applied to the VM that took its place -- rows now
carry a generation, marked BEFORE mounting, since mount_all empties the
pending children and marking after it was a race. The x1..x4 profile no
longer reached any VM. And a total of zero never said it had counted
nothing.
--- FR ---
Le type de VM était global : tout le parc en serveur, ou tout en GNOME. Il
vit désormais sur la VM, jusqu'au bout — le drapeau de création, et une
commande distante par machine à l'installation, là où une seule les
servait toutes.
Le panneau de droite n'est plus un tableau mais une rangée de widgets par
VM : vCPU, RAM, disque et type, chacun avec ses valeurs usuelles et une
saisie libre. Les champs de gauche deviennent le défaut commun, ce que
l'écran dit maintenant. Le sélecteur de portée et la modale F2 partent :
entre deux façons de faire la même chose, on garde la visible.
Trois pièges en sont sortis, et les tests les verrouillent. Les
identifiants de widgets portent un RANG, et le rang se décale quand on
coche une entrée : un événement émis par un widget déjà détruit
s'appliquait à la VM qui avait pris sa place — les rangées portent
maintenant une génération, marquée AVANT le montage, car mount_all vide
les enfants en attente et marquer après était une course. Le profil x1..x4
n'atteignait plus aucune VM. Et un total à zéro ne disait pas qu'il
n'avait rien compté.
Assisted-by: Claude Opus 5
The catalogue only offered openSUSE Tumbleweed, a rolling release with
no version number. Nearly every openSUSE incident in this series came
from that: the cloud image is a snapshot lagging behind its own repos,
hence the mandatory "zypper dup", and two VMs deployed the same day saw
git-daemon 2.54 on s390x against 2.55 on amd64. For an ERP platform,
that is not what should be offered by default.
Leap 16.0 is numbered, stable, and publishes the three architectures
that matter -- verified, x86_64, aarch64 and s390x all 200. It also
unifies its tree: no separate /ports/, unlike Tumbleweed, whose
equivalent paths 404 for Leap.
Tumbleweed stays on offer, as a bellwether for breakage to come. Two
things separate them in the scripts: the repository path, and "dup" --
which on Leap means CHANGING version, not updating.
--- FR ---
Le catalogue n'offrait qu'openSUSE Tumbleweed, une rolling sans numéro
de version. Presque tous les incidents openSUSE de la série venaient de
là : l'image cloud est un instantané en retard sur ses dépôts, d'où le
« zypper dup » obligatoire, et deux VM déployées le même jour ont vu
git-daemon 2.54 sur s390x contre 2.55 sur amd64. Pour une plateforme
ERP, ce n'est pas ce qu'on veut proposer par défaut.
Leap 16.0 est numérotée, stable, et publie les trois architectures qui
comptent — vérifié, x86_64, aarch64 et s390x en 200. Elle unifie de plus
son arbre : pas de /ports/ séparé, contrairement à Tumbleweed, dont les
chemins équivalents rendent 404 pour Leap.
Tumbleweed reste offerte, comme banc d'essai des ruptures à venir.
Deux points les séparent dans les scripts : le chemin des dépôts, et
« dup » — qui sur Leap sert à CHANGER de version, pas à mettre à jour.
Assisted-by: Claude Opus 5
Measured from Montréal on extra.db: mirror.quantum5.ca 2.0 s,
mirror.xenyth.net 7.1 s, against 8.0 s for geo.mirror.pkgbuild.com. The
official "geographic" mirror is therefore not the best one here, and
reflector, which sorts by throughput from inside the VM, had not picked
them.
Both are PREPENDED, never substituted: whatever reflector chose stays
below, as fallback. The write therefore comes after it, since "--save"
overwrites the file. Guarded on x86_64, these mirrors serving that
architecture only. Host side, the addition is idempotent.
The desktop install went through neither step: it ran without the near
mirrors and without the full update a rolling release demands, so it
pulled from Europe and broke on partial upgrades.
--- FR ---
Mesuré depuis Montréal sur extra.db : mirror.quantum5.ca 2,0 s,
mirror.xenyth.net 7,1 s, contre 8,0 s pour geo.mirror.pkgbuild.com. Le
miroir « géographique » officiel n'est donc pas le meilleur ici, et
reflector, qui trie par débit depuis la VM, ne les avait pas retenus.
Les deux sont mis EN TÊTE, jamais substitués : ce que reflector a choisi
reste dessous, en repli. L'écriture vient donc après lui, puisque
« --save » écrase le fichier. Gardé sur x86_64, ces miroirs ne servant que
cette architecture. Côté hôte, l'ajout est idempotent.
L'installation du bureau ne passait par aucune des deux étapes : elle
s'exécutait sans les miroirs proches et sans la mise à jour complète
qu'exige une rolling release, tirant donc d'Europe et cassant sur des
mises à jour partielles.
Assisted-by: Claude Opus 5
The interpreter provider is now chosen when creating a VM, in both
interfaces. "mise" installs it in the VM and lays down a precompiled
CPython; "pyenv" keeps today's build-from-source.
The question is only asked where it means something: mise publishes
binaries for amd64 and arm64 only. An all-s390x fleet never sees it, a
mixed fleet sees it along with the architectures that will fall back to
pyenv -- said before deploying, not discovered in a log an hour later.
mise goes into /usr/local/bin rather than ~/.local/bin: the remote command
runs through "ssh host 'command'", where neither ~/.profile nor ~/.bashrc
is read. Choosing "mise" exports "auto", not "mise", so a failed install
can still fall back to pyenv.
The unavailability notice was translated as "pyenv required", which said
the opposite of what happens: pyenv is the fallback, not a demand.
--- FR ---
Le fournisseur d'interpréteur se choisit désormais à la création d'une VM,
dans les deux interfaces. « mise » l'installe dans la VM et pose un
CPython précompilé ; « pyenv » garde la compilation d'aujourd'hui.
La question n'est posée que là où elle a un sens : mise ne publie de
binaires que pour amd64 et arm64. Un parc tout s390x ne la voit jamais, un
parc mixte la voit avec les architectures qui retomberont sur pyenv — dit
avant de déployer, pas découvert dans un log une heure plus tard.
mise va dans /usr/local/bin plutôt que ~/.local/bin : la commande distante
passe par « ssh hôte 'commande' », où ni ~/.profile ni ~/.bashrc ne sont
lus. Choisir « mise » exporte « auto », pas « mise », pour qu'une
installation ratée puisse encore retomber sur pyenv.
L'avis d'indisponibilité était traduit par « pyenv exigé », qui disait
l'inverse de ce qui se passe : pyenv est le repli, pas une exigence.
Assisted-by: Claude Opus 5
Tumbleweed is the only catalog entry whose qpdf already clears the pikepdf
threshold: 12.3.2 against 12.2 required, so the half-hour qpdf build under
s390x emulation never runs there. It is also the only family foreign to
both RHEL and Debian, and it brings zypper, absent from the repository:
wired into the install_dev.sh dispatch, a dependency script, the remote
bootstrap and the desktop block.
Four SUSE traps, all met on real machines. Global options precede the
subcommand, and misplaced ones abort the call; without
"--auto-agree-with-licenses" zypper waits for an answer nobody gives in a
detached install. A compat package that PROVIDES another under a different
name makes zypper raise a conflict and drop the whole batch, so only what
nothing already provides is requested. pkg-config no longer exists as an
RPM, only as a capability. And a single mirror, unreachable one day, sent
everyone back to Europe -- three are probed in order now.
CentOS Stream is dropped again: it served as a canary but 10 duplicates
what Alma and Rocky already cover.
--- FR ---
Tumbleweed est la seule entrée du catalogue dont qpdf franchit déjà le
seuil de pikepdf : 12.3.2 contre 12.2 exigé, si bien que la demi-heure de
compilation de qpdf sous émulation s390x ne s'y déclenche jamais. C'est
aussi la seule famille étrangère à RHEL comme à Debian, et elle apporte
zypper, absent du dépôt : câblé dans l'aiguillage d'install_dev.sh, un
script de dépendances, l'amorçage distant et le bloc bureau.
Quatre pièges SUSE, tous rencontrés sur machine réelle. Les options
globales précèdent la sous-commande, mal placées elles arrêtent l'appel ;
sans « --auto-agree-with-licenses » zypper attend une réponse que personne
ne donne dans une installation détachée. Un paquet compat qui FOURNIT un
autre sous un nom différent fait lever un conflit à zypper, qui abandonne
le lot entier : on ne demande donc que ce que rien ne fournit déjà.
pkg-config n'existe plus comme RPM, seulement comme capacité. Et un miroir
unique, injoignable un jour, renvoyait tout le monde en Europe — trois
sont désormais sondés dans l'ordre.
CentOS Stream repart : il servait de canari, mais 10 double ce qu'Alma et
Rocky couvrent déjà.
Assisted-by: Claude Opus 5
Fedora does publish an s390x cloud image, just elsewhere: the arch is
SECONDARY there, its images live under "fedora-secondary" rather than
/pub/fedora/linux/. The catalog comment read "404" -- it was the wrong
tree.
It brings what nothing else has on this architecture: qpdf 12.2.0,
exactly the pikepdf 10 threshold. The qpdf build, half an hour under
emulation, disappears. With GCC 15, Python 3.14 and cargo 1.90, none of
the s390x workarounds fire there.
Only 43 is kept: Fedora builds s390x for the current release only. 41 and
42 return 404 on the master mirror, 44 sits on third-party mirrors only.
Hence a positive per-architecture declaration, read by both interfaces --
the catalog never offers what deployment would refuse.
--- FR ---
Fedora publie bien une image cloud s390x, mais ailleurs : l'architecture
est SECONDAIRE chez elle, ses images vivent sous « fedora-secondary » et
non sous /pub/fedora/linux/. Le commentaire du catalogue disait « 404 » —
c'était la mauvaise arborescence.
Elle apporte ce qu'aucune autre n'a sur cette architecture : qpdf 12.2.0,
soit exactement le seuil de pikepdf 10. La compilation de qpdf, une demi-
heure sous émulation, disparaît. Avec GCC 15, Python 3.14 et cargo 1.90,
aucun des contournements s390x ne s'y déclenche.
Seule la 43 est retenue : Fedora ne construit s390x que pour la version
courante. Les 41 et 42 rendent 404 sur le miroir maître, la 44 n'est que
sur certains miroirs tiers. D'où une déclaration positive par
architecture, que les deux interfaces relisent — le catalogue ne propose
pas ce que le déploiement refuserait.
Assisted-by: Claude Opus 5
Ubuntu 20.04 and 22.04 leave EVERY architecture, not just s390x. pikepdf
needs qpdf 12.2, whose build requires C++20, while focal ships GCC 9 and
publishes no g++-10 for s390x at all. Python 3.8, node 10, cargo 0.67 and
OpenSSL 1.1.1 each had a workaround; the pile of them did not. 18.04
follows, already off the lists. The refusal lands before any apt, this
script also serving existing machines.
AlmaLinux 9 and 10, Rocky 9 and 10 join the catalog on all four
architectures: the twelve "latest" URLs were opened, with no index to
parse unlike Fedora. They would have booted unreachable though -- the
cloud-config forced "groups: users, sudo", but the RHEL family has no
sudo group, only wheel, and an unknown group makes useradd fail, hence no
password and no key. The very trap already known for Debian, repeated
elsewhere. Host side, EPEL and CRB are enabled: without them most -devel
packages are missing, silently.
The server / graphical choice gains Cinnamon, the Linux Mint desktop,
from the distribution's own repositories. Mint's repository is set aside:
plain HTTP, and i386/amd64 only, which would rule out arm64 and s390x.
Along the way, dnf now installs an ENVIRONMENT rather than a group --
"gnome-desktop" brings gdm and gnome-shell but not base-x, hence no X
server.
--- FR ---
Ubuntu 20.04 et 22.04 partent de TOUTES les architectures, pas seulement
de s390x. pikepdf réclame qpdf 12.2, dont la compilation exige C++20,
quand focal livre GCC 9 et ne publie même pas de g++-10 pour s390x.
Python 3.8, node 10, cargo 0.67 et OpenSSL 1.1.1 avaient chacun leur
contournement ; leur accumulation, non. 18.04 suit, déjà hors des
listes. Le refus tombe avant tout apt, ce script servant aussi les
machines existantes.
AlmaLinux 9 et 10, Rocky 9 et 10 entrent au catalogue, sur les quatre
architectures : les douze URL « latest » ont été ouvertes, aucun index à
analyser contrairement à Fedora. Elles auraient pourtant démarré
inaccessibles — le cloud-config imposait « groups: users, sudo », or la
famille RHEL n'a pas de groupe sudo mais wheel, et un groupe inconnu fait
échouer useradd, donc ni mot de passe ni clé. C'est le piège déjà connu
pour Debian, reproduit ailleurs. Côté hôte, EPEL et CRB sont activés :
sans eux la plupart des -devel manquent, en silence.
Le choix serveur / graphique gagne Cinnamon, le bureau de Linux Mint,
depuis les dépôts de la distribution. Le dépôt de Mint lui-même est
écarté : il est en HTTP nu et ne publie que i386 et amd64, ce qui
exclurait arm64 et s390x. Au passage, dnf installe désormais un
ENVIRONNEMENT et non un groupe — « gnome-desktop » apporte gdm et
gnome-shell mais pas base-x, donc pas de serveur X.
Assisted-by: Claude Opus 5
VMs could only be servers. A server-or-graphical choice joins both
interfaces, installing GNOME along with remote access to it.
Packages come from the remote command, not cloud-init: their 1 to 2 GB
would stretch an already long boot there while leaving no trace in the
monitoring, and group installs do not go through it. The desktop
therefore does not depend on ERPLibre -- a VM may be wanted graphical
and bare.
Each distribution has its own names, taken from the source: Arch has no
xrdp in its official repositories and takes TigerVNC. The SPICE display
is set only on amd64 and arm64; s390x does expose virtio-gpu-ccw, but
nothing guarantees its kernel's DRM driver, whereas remote desktop works
everywhere.
--- FR ---
Les VM ne pouvaient être que des serveurs. Un choix serveur ou graphique
s'ajoute aux deux interfaces, et pose GNOME avec son accès distant.
Les paquets viennent de la commande distante, pas de cloud-init : leurs
1 à 2 Go y allongeraient un démarrage déjà long sans laisser de trace
dans le suivi, et les installations par groupe n'y passent pas. Le bureau
ne dépend donc pas d'ERPLibre — une VM peut être voulue graphique et nue.
Chaque distribution a ses noms, relevés à la source : Arch n'a pas xrdp
dans ses dépôts officiels et prend TigerVNC. L'écran virtuel SPICE n'est
posé que sur amd64 et arm64 ; s390x expose bien virtio-gpu-ccw, mais rien
ne garantit le pilote DRM de son noyau, alors que le bureau distant, lui,
marche partout.
Assisted-by: Claude Opus 5
Changing state meant retyping the name, on a single comma-separated line.
Names are long and alike -- two of them sometimes differ by a suffix
only -- and a typo here decided whether a machine started or shut down.
The screen now reuses the numbered list from the delete screen, "all"
included, and its parser, which already accepts names: nothing is lost
for those who typed them. The warning on an unknown entry is kept, that
parser otherwise dropping it without a word.
--- FR ---
Changer l'état demandait de retaper le nom, sur une seule ligne séparée
par des virgules. Les noms sont longs et se ressemblent — deux d'entre
eux ne diffèrent parfois que par un suffixe — et une faute de frappe
portait ici sur le démarrage ou l'extinction d'une machine.
L'écran reprend donc la liste numérotée de la suppression, « all »
compris, et son analyseur, qui accepte déjà les noms : rien n'est perdu
pour qui les tapait. L'avertissement sur une entrée inconnue est
conservé, cet analyseur l'écartant autrement sans un mot.
Assisted-by: Claude Opus 5
The form's parallelism was pinned at 4, while the CLI already counted
host cores: two interfaces, two answers.
A box ticked by default now gives one run per install -- five VMs, five
deployments -- and the core count stops capping it. Unticking hands
control back to the dropdown, whose default finally follows the host. The
CLI offers the same toggle through "n".
--- FR ---
Le parallélisme du formulaire était figé à 4, quand la CLI comptait déjà
les cœurs de l'hôte : deux interfaces, deux réponses.
Une case cochée par défaut donne désormais une exécution par
installation — cinq VM, cinq déploiements — et le nombre de cœurs cesse
alors de plafonner. La décocher rend la main à la liste, dont le défaut
suit enfin l'hôte. La CLI offre la même bascule par « n ».
Assisted-by: Claude Opus 5
Monitoring stayed on the same address for 1170 s, never catching up with
the VM. Two holes, in the very re-resolution meant to prevent that.
The lease fallback required an answer on port 22. But dnsmasq keeps one
lease per MAC: when cloud-init sets the real hostname and the DHCP client
asks again, the lease MOVES the address. The old one no longer belongs to
the VM and sshd will never answer there. The lease therefore wins as soon
as it stops listing the current address.
The other hole explains the silence: virsh was muted on both branches, so
an unreachable libvirt kept the initial IP without a single line saying
so. The log went quiet for a quarter of an hour for the same reason --
cloud-init holds the package lock while writing nothing.
--- FR ---
Le suivi restait sur la même adresse pendant 1170 s, sans jamais rattraper
la VM. Deux trous, dans la re-résolution censée l'éviter.
Le repli par bail exigeait une réponse sur le port 22. Or dnsmasq garde un
bail par MAC : quand cloud-init pose le vrai nom d'hôte et que le client
DHCP redemande, le bail DÉPLACE l'adresse. L'ancienne n'appartient plus à
la VM et sshd n'y répondra jamais. Le bail l'emporte donc dès qu'il cesse
de lister l'adresse courante.
L'autre trou explique le silence : virsh était muet sur les deux branches,
si bien qu'un libvirt injoignable conservait l'IP initiale sans une ligne
pour le dire. Le log se taisait un quart d'heure pour la même raison —
cloud-init tient le verrou des paquets sans rien écrire.
Assisted-by: Claude Opus 5
Two blockers on 20.04, neither worth carrying. "TypeError: unsupported
operand type(s) for |" fired before the install even started:
update_env_version.py imports erplibre_state as the very first thing
"make install_os" does, hence before pyenv, on the system Python 3.8,
where a "str | None" annotation is evaluated at import. And cryptography
now demands OpenSSL 3, while focal ships 1.1.1.
Annotations are therefore deferred, a contract the bootstrap file already
stated in a comment and now holds across script/version and
script/install. But the releases themselves go: pikepdf requires
qpdf >= 12.2, compiled in C++20, when focal ships GCC 9.
--- FR ---
Deux blocages sur 20.04, aucun ne méritant d'être porté. « TypeError:
unsupported operand type(s) for | » se déclenchait avant même le début de
l'installation : update_env_version.py importe erplibre_state en tout
premier lieu de « make install_os », donc avant pyenv, sur le Python 3.8
du système, où une annotation « str | None » est évaluée à l'import. Et
cryptography exige désormais OpenSSL 3, quand focal livre 1.1.1.
Les annotations sont donc différées, contrat que le fichier d'amorçage
énonçait déjà en commentaire et qui tient maintenant sur script/version et
script/install. Mais les versions elles-mêmes partent : pikepdf réclame
qpdf >= 12.2, compilé en C++20, quand focal livre GCC 9.
Assisted-by: Claude Opus 5
Installs run detached (setsid -f): closing the terminal does not stop
them, but it lost the only view onto them. With no way to resume, the
only way out was deleting the VMs and starting over.
Picking "Deploy" now looks at the latest run: if VMs there still lack an
exit marker, it offers to reopen its monitoring instead of starting
another. Time since the last write is shown, a dead run being otherwise
indistinguishable from a live one.
Only the latest run is examined: an old one left without a marker would
flag a phantom install forever. Dry-run creates nothing, so it does not
ask. "Reopen monitoring" moves to the Deployment section, where one
looks for it.
--- FR ---
Les installs partent détachées (setsid -f) : fermer le terminal ne les
arrête pas, mais faisait perdre la seule vue dessus. Sans moyen de
reprendre, la seule issue était d'effacer les VM et de recommencer.
Choisir « Déployer » regarde donc le dernier run : s'il lui reste des VM
sans marqueur de sortie, il propose de rouvrir son suivi plutôt que d'en
lancer un autre. Le silence depuis la dernière écriture est affiché, un
run mort n'étant pas distinguable autrement d'un run vivant.
Seul le dernier run est examiné : un run ancien laissé sans marqueur
signalerait éternellement une install fantôme. L'aperçu ne crée rien, il
ne pose pas la question. « Rouvrir le suivi » rejoint la section
Déploiement, où on le cherche.
Assisted-by: Claude Opus 5
The CLI already took a typed value -- a letter picks a suggestion, a
digit stands for itself. The form, though, locked the custom profile
into its three dropdowns.
Each now gets a final "free value..." entry revealing an input below it.
An invalid entry overwrites nothing: the resource falls back to the
catalog value instead of freezing the view.
The 3 vCPU preset was missing too, between 2 and 4; it comes from the
same constant, so both interfaces gain it together.
--- FR ---
La CLI acceptait déjà une valeur tapée — une lettre choisit une
suggestion, un chiffre vaut pour lui-même. Le formulaire, lui, enfermait
le profil personnalisé dans ses trois listes déroulantes.
Chacune reçoit donc un dernier choix, « valeur libre… », qui révèle une
saisie sous elle. Une entrée invalide n'écrase rien : la ressource
retombe sur celle du catalogue plutôt que de bloquer la vue.
Le préréglage 3 vCPU manquait aussi, entre 2 et 4 ; il vient de la même
constante, donc les deux interfaces l'offrent ensemble.
Assisted-by: Claude Opus 5
Four papercuts met while using the CLI: the test menu returned a verdict
nobody could find at the end of the output, the file browser never closed
once a file was picked, a wrong KeePass password killed the whole CLI, and
a configuration key had drifted from the code that reads it.
--- FR ---
Quatre irritants rencontrés à l'usage : le menu Test rendait un verdict
introuvable en fin de sortie, le navigateur de fichiers ne se fermait
jamais une fois le fichier choisi, un mauvais mot de passe KeePass tuait
tout le CLI, et une clé de configuration avait dérivé du code qui la lit.
Assisted-by: Claude Opus 5
An IMAP/SMTP client in the menu, with its tests against real servers. Most
of the work went into refusals: an application password is named only when
the server actually refuses, an accented password is reported as never
having left the machine, and a refusal is recognised by what the server
SAYS rather than by matching its wording. A malformed date no longer takes
the whole folder down, and a received email is never read as markup.
--- FR ---
Un client IMAP/SMTP dans le menu, avec ses tests contre de vrais serveurs.
L'essentiel du travail porte sur les refus : le mot de passe d'application
n'est nommé que lorsque le serveur refuse vraiment, un mot de passe accentué
est signalé comme n'ayant jamais quitté la machine, et un refus se reconnaît
à ce que le serveur DIT plutôt qu'à ses mots. Une date illisible n'emporte
plus le dossier entier, et un courriel reçu n'est jamais lu comme du balisage.
Assisted-by: Claude Opus 5
The monitor froze the address given at launch, so it lost the VM as soon as
cloud-init renamed the host and DHCP handed out another lease. It now
re-resolves at each attempt, in the views too, and reads virsh without sudo
— « sudo -n » fails in a detached session with no tty.
First boot also stopped paying for what it does not need: the guest agent
leaves cloud-init, snapd and locale-gen go, apt takes the fastest mirror.
The timezone follows the host. And when KVM is missing, the deployment says
so before the wait instead of being mysteriously fifteen times slower.
--- FR ---
Le suivi figeait l'adresse connue au lancement : il perdait donc la VM dès
que cloud-init posait le vrai nom d'hôte et que DHCP donnait un autre bail.
Il la ré-résout désormais à chaque tentative, dans les vues aussi, et lit
virsh sans sudo — « sudo -n » échoue dans une session détachée, sans tty.
Le premier démarrage cesse aussi de payer l'inutile : l'agent invité sort
de cloud-init, snapd et locale-gen disparaissent, apt prend le miroir le
plus rapide. Le fuseau suit l'hôte. Et faute de KVM, le déploiement le dit
avant l'attente, au lieu d'être quinze fois plus lent sans raison visible.
Assisted-by: Claude Opus 5
A read-only toolkit answering « what is in this database, and what will
break on a version bump »: schema size and tables no model claims,
customised views compared with their module source, and the x_ fields
Studio left behind. It reads a backup zip directly, so a 40 GB dump needs
no restore, and it reaches every answer from the TODO menu.
--- FR ---
Une boîte à outils en lecture seule qui répond à « qu'y a-t-il dans cette
base, et qu'est-ce qui cassera à la montée de version » : taille du schéma
et tables qu'aucun modèle ne réclame, vues personnalisées comparées à leur
source, champs x_ laissés par Studio. Elle lit un zip de sauvegarde tel
quel, donc sans restaurer, et tout s'atteint depuis le menu TODO.
Assisted-by: Claude Opus 5
Groups the long menus into sections with icons, accepts o/oui everywhere,
shows the default of each yes/no question, and stops offering a traceback
line as a database name when PostgreSQL is unreachable.
--- FR ---
Regroupe les menus longs en sections avec icônes, accepte o/oui partout,
affiche la valeur par défaut de chaque question oui/non, et cesse de
proposer une ligne de trace d'appel comme nom de base quand PostgreSQL est
injoignable.
Assisted-by: Claude Opus 4.8
Assisted-by: Claude Opus 5
Stores the per-user settings in ~/.erplibre/todo_prefs.json: which deploy
interface to use, what to display while deploying, which migration
interface. A missing or corrupt file reads as defaults.
--- FR ---
Range les réglages propres à l'utilisateur dans
~/.erplibre/todo_prefs.json : interface de déploiement, affichage pendant
le déploiement, interface de migration. Un fichier absent ou corrompu se
lit comme les valeurs par défaut.
Assisted-by: Claude Opus 4.8
Records the menus visited and shows them as a tree, a kanban or a list.
The tree is derived from the code, so a command never visited appears too,
and can be run from there.
--- FR ---
Enregistre les menus visités et les présente en arbre, en kanban ou en
liste. L'arbre est dérivé du code, si bien qu'une commande jamais visitée
y figure aussi, et peut être lancée de là.
Assisted-by: Claude Opus 4.8
Deploy, list, test, resize, delete and clean up VMs. Adds the SSH
configuration with recursive ProxyJump, port forwarding, and registration
of the QEMU hosts in virt-manager.
--- FR ---
Déployer, lister, tester, redimensionner, supprimer et nettoyer des VM.
Ajoute la configuration SSH avec ProxyJump récursif, la redirection de port
et l'enregistrement des hôtes QEMU dans virt-manager.
Assisted-by: Claude Opus 4.8