Commit graph

143 commits

Author SHA1 Message Date
db9472ef69 [FIX] proxmox : l'ancienne entrée ssh s'en va avec la convention
Le nom chaîné devient systématique, mais les entrées écrites AVANT portent le
nom court — et rien ne les retirerait : elles ne déclarent pas le nom qu'on
écrit maintenant. Deux blocs mèneraient à la même machine, exactement ce
qu'on venait d'enlever.

Le ProxyJump tranche : un bloc qui rebondit par CET hôte est le nôtre, on le
retire. Celui d'une VM locale homonyme n'en a pas, et on n'y touche jamais ;
celui d'un autre hôte Proxmox non plus.

Le drapeau Odoo gagne son test au passage. Il tombait pour la même raison que
les colonnes vides — la sonde est le dernier maillon de la suite distante, et
un parc où une seule VM n'a pas d'Odoo, un hyperviseur imbriqué par exemple,
finit en échec. Vérifié sur les trois VM : l'hôte rend bien « ODOO » pour les
deux qui écoutent, et le navigateur répondait 303 pendant que la colonne
disait « — ».

--- EN ---

The chained name becomes systematic, but entries written BEFORE carry the
short one — and nothing would retire them: they do not declare the name we
now write. Two blocks would lead to the same machine, exactly what we had
just removed.

The ProxyJump decides: a block hopping through THIS host is ours, so it goes.
A local namesake's has none, and is never touched; another Proxmox host's
neither.

The Odoo flag gains its test along the way. It failed for the same reason as
the empty columns — the probe is the remote pipeline's last link, and a fleet
where a single VM has no Odoo, a nested hypervisor for instance, ends in
failure. Verified on all three VMs: the host does return "ODOO" for the two
that listen, and the browser answered 303 while the column said "—".

Assisted-by: Claude Opus 5
2026-08-25 03:31:12 -04:00
f31e2370a7 [FIX] suivi : un relevé Proxmox jeté, et deux lignes qui montraient une autre machine
Sur trois VM d'un même Proxmox, une seule avait ses colonnes vides — et les
deux autres montraient les chiffres d'une AUTRE machine. Deux fautes, dont
une était le miroir d'un correctif précédent.

Le code de sortie de la suite distante est celui de son DERNIER maillon, la
sonde Odoo. Tant qu'Odoo n'écoute pas — c'est-à-dire pendant TOUTE
l'installation, précisément quand on regarde — la boucle finit en échec et le
relevé, parfait, était jeté. On avait corrigé l'erreur inverse, un code 0 pris
pour une réponse ; exiger 0 était la même faute retournée. Seule une liste de
ressources analysable prouve une réponse.

Pendant ce temps, « virsh domstats » indexe par NOM, et un nom se partage :
les deux VM qui avaient un homonyme LOCAL affichaient ses chiffres. Mesuré —
1,5 Gio de RAM sur 12 et 58 Gio de disque sur 65, quand la vraie tournait avec
3 Gio et 25. Les relevés locaux d'une VM qui vit ailleurs sont donc retirés
AVANT d'ajouter ceux de l'hôte : un hôte muet laisse la colonne VIDE, ce qui
est vrai. Une colonne vide se remarque ; une colonne juste et fausse, non.

L'alias enfin. Prendre le nom court quand il se trouvait libre donnait un parc
incohérent : sur ce même déploiement, deux VM ont reçu « hôte+vm » — leurs
noms étaient pris par des domaines locaux — et la troisième son nom court. Une
convention qui dépend de ce qui traîne dans le fichier n'est pas une
convention. Le nom chaîné est systématique.

--- EN ---

Of three VMs on one Proxmox, only one had empty columns — and the other two
showed ANOTHER machine's figures. Two defects, one the mirror of an earlier
fix.

A remote pipeline's exit code is its LAST link's, the Odoo probe. While Odoo
is not listening — that is, during the WHOLE install, exactly when you are
watching — the loop ends in failure and the reading, perfectly good, was
thrown away. We had fixed the opposite error, a 0 taken for an answer;
demanding 0 was the same mistake reversed. Only a parsable resource list
proves an answer.

Meanwhile "virsh domstats" indexes by NAME, and a name is shared: the two VMs
with a LOCAL namesake displayed its figures. Measured — 1.5 GiB of RAM out of
12 and 58 GiB of disk out of 65, while the real one ran on 3 GiB and 25. Local
readings for a VM that lives elsewhere are therefore dropped BEFORE the host's
are added: a silent host leaves the column EMPTY, which is true. An empty
column gets noticed; a plausible wrong one does not.

The alias, finally. Taking the short name while it happened to be free gave an
inconsistent fleet: in that same deployment two VMs got "host+vm" — their
names were held by local domains — and the third its short name. A convention
that depends on what happens to sit in the file is not a convention. The
chained name is now systematic.

Assisted-by: Claude Opus 5
2026-08-25 03:31:12 -04:00
5e75f3da31 [FIX] proxmox : un seul nom par entrée ~/.ssh/config, et le bon
L'entrée portait deux noms sur sa ligne « Host » — le chaîné « hôte+vm » et
le court : « Host erplibre-proxmox-9+erplibre-arch-latest
erplibre-arch-latest ». Le second est un doublon dès que le premier suffit.
ssh n'a besoin que d'un nom ; le doubler n'ajoute qu'une façon de plus
d'écrire la même adresse. Rapporté.

Un seul, donc, et choisi : le nom court quand il est LIBRE, c'est celui qu'on
tape ; le chaîné quand il désignerait une autre machine — une VM locale
homonyme, ou la VM d'un autre hôte Proxmox. Ce qui a forcé le changement est
nommé à l'écran plutôt que laissé en surprise.

« Pris » se juge sur le ProxyJump du bloc et non sur sa seule présence. Le
test l'a montré avant l'usage : notre propre entrée, réécrite à chaque
déploiement, se prenait pour une rivale et le nom basculait d'une fois sur
l'autre.

--- EN ---

The entry carried two names on its "Host" line — the chained "host+vm" and
the short one: "Host erplibre-proxmox-9+erplibre-arch-latest
erplibre-arch-latest". The second is redundant as soon as the first is
enough. ssh needs one name; doubling it only adds another way to write the
same address. Reported.

One name then, and a chosen one: the short one while it is FREE, since that
is what you type; the chained one when it would point at another machine — a
local VM of the same name, or another Proxmox host's VM. Whatever forced the
change is named on screen rather than left as a surprise.

"Taken" is judged on the block's ProxyJump, not on its mere presence. The
test showed it before use: our own entry, rewritten at every deployment, took
itself for a rival and the name flipped from one run to the next.

Assisted-by: Claude Opus 5
2026-08-25 03:28:39 -04:00
9a7b8cb36f [ADD] analyse: l'état d'une instance, lu pour l'usage qu'on en fait
Le même chiffre veut dire deux choses opposées. Zéro cron actif est le
succès attendu d'une copie et une panne totale sur une production. Un
rapport qui ignore cela crie au loup sur ce qu'on vient de demander, et
l'on cesse de le lire. L'attente est donc déclarée, copy ou live, et
chaque contrôle dit ce qu'il juge sous l'une et sous l'autre.

Deux contrôles ont été ÉCARTÉS sous copy après mesure : sur la base 12
d'origine, jamais démarrée, 11 crons étaient déjà en retard et db_backup
déjà vide — notre propre update_prod_to_dev les efface. Les afficher en
rouge aurait été du bruit ; en vert, un mensonge. Ils sont montrés non
jugés, avec la raison.

Le code Python en base a été mesuré et abandonné : 132 actions serveur,
zéro citant un modèle inexistant, et les 3 « modèles sans table » sont
ir.autovacuum et deux autres modèles abstraits d'Odoo.

--- EN ---

The same number means two opposite things. Zero active cron is the
expected success of a copy and a total outage on production. A report
that ignores this cries wolf over what was just requested, and stops
being read. The expectation is therefore declared, copy or live, and each
check states what it judges under either.

Two checks were DROPPED under copy after measuring: on the untouched 12
source database, 11 crons were already late and db_backup already empty —
our own update_prod_to_dev deletes them. Red would have been noise; green
a lie. They are shown unjudged, with the reason.

In-database Python was measured and dropped: 132 server actions, none
naming a missing model, and the 3 "models without a table" are
ir.autovacuum and two other Odoo abstract models.

Assisted-by: Claude Opus 5
2026-08-25 03:28:39 -04:00
2521267896 [ADD] analyse: ausculter une base qui n'est pas ici
Les analyses existaient ; le chemin d'AVANT manquait. La base d'un client
est dans un zip, derrière une URL, ou vivante sur un serveur.

« Restant de migration » a dû être écrit : check_migration_quality compare
les bases de PALIER et exige le journal de progression — devant une
sauvegarde isolée, ni l'un ni l'autre n'existe.

Ses compteurs évidents ont été écartés après mesure. Comparés à la base
d'ORIGINE : champs sans colonne 25 → 72, modèles sans table 90 → 158.
Vingt-cinq et quatre-vingt-dix AVANT toute migration : du bruit. Ne
restent que les constats faux en eux-mêmes, 0 avant, non nuls après —
9 langues au drapeau NULL, 68 tables m2m absentes, 414 index doublés.

Le passe-plat RPC n'accepte que la lecture. psql l'obtient du serveur ;
une session RPC n'a rien d'équivalent, et la liste blanche est donc
appliquée dans le passe-plat, pas chez l'appelant.

--- EN ---

The analyses existed; the path BEFORE them did not. A customer database
sits in a zip, behind a URL, or live on a server.

« Migration leftovers » had to be written: check_migration_quality
compares STEP databases and needs the progression log — facing a lone
backup, neither exists.

Its obvious counters were dropped after measuring. Against the ORIGINAL
database: fields with no column 25 → 72, models with no table 90 → 158.
Twenty-five and ninety BEFORE any migration: noise. Only what is wrong in
itself remains, 0 before and non-zero after — 9 languages with a NULL
flag, 68 missing m2m tables, 414 duplicated indexes.

The RPC proxy only reads. psql gets that from the server; an RPC session
has no equivalent, so the allowlist lives in the proxy, not the caller.

Assisted-by: Claude Opus 5
2026-08-25 03:28:39 -04:00
0cfb6f6a00 [FIX] todo : rendre au découpage ses colonnes de télémétrie
« Il manque plein d'informations qu'il y avait avant » : l'écran de
télémétrie construit son arbre en LISANT le code — un fichier, sa première
classe. Depuis que les menus QEMU/KVM et Proxmox vivent dans des mixins,
leurs colonnes avaient disparu de cet écran ; les commandes s'exécutaient
toujours, mais on ne pouvait plus les lancer de là. L'arbre lit maintenant
aussi les mixins, trouvés dans les imports de todo.py — un mixin ajouté
demain apparaîtra sans qu'on y pense.

Le menu Proxmox n'avait pas d'étiquette : son fil d'Ariane s'arrêtait deux
niveaux plus haut, sur « Deploy ». Vérifié : 16 commandes QEMU/KVM et 18
Proxmox dans l'arbre, contre zéro et zéro.

--- EN ---

"A lot of information that used to be there is missing": the telemetry screen
builds its tree by READING the code — one file, its first class. Since the
QEMU/KVM and Proxmox menus moved into mixins, their columns had vanished from
that screen; the commands still ran, but could no longer be launched from
there. The tree now reads the mixins too, found in todo.py's own imports — a
mixin added tomorrow shows up without anyone thinking about it.

The Proxmox menu had no label: its breadcrumb stopped two levels up, at
"Deploy". Verified: 16 QEMU/KVM commands and 18 Proxmox ones in the tree,
against zero and zero.

Assisted-by: Claude Opus 5
2026-08-25 03:19:18 -04:00
562ad1c873 [ADD] todo : dire la place qui reste sous le plan de déploiement
La ligne de totaux annonçait « ~126 G » sans dire sur quoi : la demande
seule ne dit pas si ça rentre, et on l'apprenait au déploiement. Elle dit
maintenant la demande, ce qui reste et la capacité — « ~126 G / 20 G libres
sur 270 G » — et prévient dès que le plan dépasse. Les trois limites (RAM,
disque, cœurs) s'affichent ensemble : n'en montrer qu'une cachait les
autres. Sur Proxmox, la place vient du stockage choisi, que « pvesm status »
donnait déjà.

Deux défauts trouvés en le faisant : la marque de génération, exigée de tous
les widgets, faisait taire chaque réglage commun de l'écran Proxmox, et
« [x1] » disparaissait, lu comme une balise Rich.

--- EN ---

The totals line said "~126 G" without saying out of what: the demand alone
does not tell whether it fits, and one found out at deploy time. It now says
the demand, what is left and the capacity — "~126 G / 20 G free of 270 G" —
and warns as soon as the plan exceeds it. The three limits (RAM, disk,
cores) show together: showing only one hid the others. On Proxmox the room
comes from the chosen storage, which "pvesm status" already gave.

Two defects found on the way: the generation mark, required of every widget,
silenced each common setting on the Proxmox screen, and "[x1]" vanished,
read as a Rich tag.

Assisted-by: Claude Opus 5
2026-08-25 03:17:13 -04:00
0910e3ea06 [FIX] todo : nettoyer ce que le découpage a laissé derrière
Deux imports de todo.py n'avaient plus d'usager : « grp » est parti avec le
code qui posait les droits d'un groupe, et « getpass » est refait localement
par la seule fonction qui s'en sert. Trois fichiers neufs avaient leurs
imports dans le désordre, que le prochain « make format » aurait reformatés
en salissant un diff sans rapport.

--- EN ---

Two of todo.py's imports had no user left: « grp » went with the code that
set a group's rights, and « getpass » is re-imported locally by the only
function that uses it. Three new files had their imports out of order, which
the next « make format » would have reshuffled, dirtying an unrelated diff.

Assisted-by: Claude Opus 5
2026-08-25 03:17:13 -04:00
95e70150c3 [REF] todo : un fichier par sujet, un socle par formulaire
todo.py passait 13 000 lignes : plus personne n'y trouvait où une chose
vivait. Il en garde 4 400 — les menus et les aides générales — et six
fichiers portent chacun un sujet, assemblés en mixins sur la classe TODO.
Aucun membre perdu : 438 avant, 438 après, et treize sources modifiées
seulement là où un appel de classe devait changer de nom.

Les deux formulaires de déploiement posent le même travail : ils partagent
maintenant la logique pure, le CSS, la fabrique des rangées de ressources
et les gestes du plan (surcharges, verrous, exemplaires, renommage).
Vérifié en rendant le formulaire QEMU avant et après, sur neuf gestes :
même écran au SVG près, même état, même spec.

--- EN ---

todo.py had passed 13,000 lines: nobody could find where anything lived.
It keeps 4,400 — the menus and the general helpers — and six files each
own one subject, assembled as mixins on the TODO class. No member lost:
438 before, 438 after, and thirteen sources changed only where a
class-level call had to change name.

Both deployment forms do the same work: they now share the pure logic, the
CSS, the resource-row factory and the plan's gestures (overrides, locks,
copies, renaming). Verified by rendering the QEMU form before and after
across nine gestures: same screen down to the SVG, same state, same spec.

Assisted-by: Claude Opus 5
2026-08-25 03:16:07 -04:00
01b77dfa80 [ADD] proxmox : déployer des VM sur un hôte Proxmox distant
Nouvelle entrée sous QEMU/KVM, avec l'équivalent de ses dix-sept commandes.
Toute la différence tient en une phrase : l'hyperviseur est ailleurs. On
choisit donc l'hôte — VM QEMU locale, adresse, ou ~/.ssh/config — et on le
vérifie : pveversion le prouve, id/sudo décident du privilège, et une clé
d'hôte inconnue s'enregistre par ssh-keyscan plutôt qu'en désactivant le
contrôle.

Quatre pièges trouvés sur un hôte réel. Une Proxmox installée sur Debian n'a
aucun pont : on en propose un INTERNE, car ajouter l'interface physique
déplace l'adresse de l'hôte et coupe la session — à distance, sans retour.

--- EN ---

A new entry under QEMU/KVM, with the counterpart of its seventeen commands.
The whole difference fits in one sentence: the hypervisor is elsewhere. So the
host is chosen — local QEMU VM, address, or ~/.ssh/config — and then checked:
pveversion proves it, id/sudo decide about privilege, and an unknown host key
is recorded with ssh-keyscan rather than by disabling the check.

Four traps found on a real host. A Proxmox installed on Debian has no bridge:
we offer an INTERNAL one, because adding the physical NIC moves the host
address and cuts the session — remotely, with no way back.

Assisted-by: Claude Opus 5
2026-08-23 05:54:38 -04:00
422094ca07 [UPD] format todo 2026-08-23 04:17:34 -04:00
3c10ca2eb2 [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
7923e37e4f [ADD] analyse: qui dépend de qui, à l'écran
Le menu Analyse disait quels modules manquent, jamais qui dépend de qui.
« Puis-je retirer celui-ci » se réglait donc à la main : au palier
17 → 18, il a fallu écrire la requête pour savoir si web_responsive
pouvait partir.

L'écran liste les modules et « d » parcourt quatre relations : ce dont il
dépend, ce qui en dépend, tout ce qu'il entraîne, tout ce qui tombe avec
lui. « f » filtre, « / » cherche — une base en porte trois mille. La
réponse qui compte est écrite en toutes lettres : deux dépendants
déclarés dont zéro installé, c'est un retrait sans danger.

--- EN ---

The Analyse menu told which modules were missing, never which depends on
which. « Can I remove this one » was therefore answered by hand: at the
17 → 18 step we had to write the query to learn whether web_responsive
could go.

The screen lists the modules and « d » walks four relations: what it
needs, what needs it, everything it pulls in, everything that falls with
it. « f » filters, « / » searches — a database holds three thousand of
them. The answer that matters is spelled out: two declared dependents of
which zero installed means removal is safe.

Assisted-by: Claude Opus 5
2026-08-23 03:46:08 -04:00
f12e79c3a9 [ADD] qemu : déployer Proxmox VE (amd64, arm64)
Proxmox ne publie aucune image cloud : son ISO est un installateur qui formate
le disque. On prend donc la voie que l'amont documente lui-même — Proxmox VE
sur Debian — depuis l'image cloud trixie, partagée avec un déploiement
Debian 13 au lieu d'être téléchargée deux fois.

s390x n'y est pas et n'y sera pas par cette voie : le dépôt n'a aucun index
binary-s390x. arm64 y est, officiel depuis PVE 9. Le catalogue le dit AVANT le
déploiement, au lieu d'échouer au premier apt.

Vérifié sur une VM réelle : pve-manager 9.2.11, noyau 7.0.14-12-pve, quatre
services actifs, interface web en HTTP 200.

--- EN ---

Proxmox publishes no cloud image: its ISO is an installer that formats the
disk. So we take the path upstream documents itself — Proxmox VE on Debian —
from the trixie cloud image, shared with a Debian 13 deployment instead of
being downloaded twice.

s390x is not there and will not be by this route: the repository has no
binary-s390x index. arm64 is, official since PVE 9. The catalog says so BEFORE
the deployment rather than failing at the first apt.

Verified on a real VM: pve-manager 9.2.11, kernel 7.0.14-12-pve, four services
active, web UI answering HTTP 200.

Assisted-by: Claude Opus 5
2026-08-23 03:28:03 -04:00
5da0ebaed3 [FIX] sshfs : n'annoncer un montage que s'il a eu lieu
Après un code 1, le menu affichait « Monté sur … », la commande pour démonter
et celle pour ouvrir le répertoire : le montage n'avait pas eu lieu, et on
cherchait des fichiers dans un répertoire vide. Le succès ne s'affiche plus
qu'en cas de succès, et le point de montage inutilisé est retiré.

L'échec, lui, avait une cause : sshfs lit « a+b » comme un chaînage d'hôtes et
ne consulte jamais ~/.ssh/config pour l'alias entier. Or c'est todo.py qui
nomme les VM « rebond+domaine », et cette seconde moitié est un domaine
libvirt, pas un alias SSH du rebond. On résout donc l'alias par « ssh -G » et
on rend à sshfs une cible qu'il ne peut plus mal lire, ProxyJump comprise.

--- EN ---

After exit code 1, the menu still printed "Mounted on …", the unmount command
and the file-manager command: nothing had been mounted, and one went looking
for files in an empty directory. The success block now only prints on success,
and the unused mount point is removed.

The failure itself had a cause: sshfs reads "a+b" as host chaining and never
consults ~/.ssh/config for the whole alias. But todo.py is what names VMs
"jump+domain", and that second half is a libvirt domain, not an SSH alias on
the jump host. So we resolve the alias with "ssh -G" and hand sshfs a target
it can no longer misread, ProxyJump included.

Assisted-by: Claude Opus 5
2026-08-23 02:20:15 -04:00
3f97b0a49c [FIX] security: the KeePass password leaves the command line too
Same exposure as the master password, same fix. kdbx_manager put the Odoo
password straight into the web_login command; /proc/<pid>/cmdline is
readable by every user on the machine, and no downstream filter reaches
that.

The command now carries the NAME of an environment variable, never the
value. One name per entry, because several credentials go out in a single
"parallel" call and a single variable could not tell them apart.
get_extra_command_user therefore returns (fragments, variables), and the
two call sites hand the variables to exec_command_live, which already
merged an environment.

Two things found on the way. web_login re-sent config.default_password_auth
when it retried after dismissing a modal, ignoring whatever the caller had
passed -- the retry silently fell back to "admin". And install_forgejo
printed the admin password back to the terminal, hence into the install log
and any CI capture; its own header already documents the default.

A test pins the guarantee: the fragment must not contain the password.

--- FR ---

Même exposition que pour le mot de passe maître, même correctif.
kdbx_manager mettait le mot de passe Odoo directement dans la commande
web_login ; /proc/<pid>/cmdline est lisible par tout utilisateur de la
machine, et aucun filtre en aval ne l'atteint.

La commande porte désormais le NOM d'une variable d'environnement, jamais
la valeur. Un nom par entrée, car plusieurs identifiants partent dans un
seul appel « parallel » et une variable unique ne saurait les distinguer.
get_extra_command_user rend donc (fragments, variables), et les deux
appelants confient les variables à exec_command_live, qui fusionnait déjà
un environnement.

Deux trouvailles en chemin. web_login renvoyait config.default_password_auth
à la reprise après une modale, ignorant ce que l'appelant avait fourni — la
reprise retombait en silence sur « admin ». Et install_forgejo réaffichait
le mot de passe administrateur, donc dans le journal d'installation et
toute capture de CI ; son propre en-tête documente déjà le défaut.

Un test verrouille la garantie : le fragment ne doit pas porter le secret.

Assisted-by: Claude Opus 5
2026-08-23 02:11:50 -04:00
5b748e347e [ADD] make: une cible pour les tests unitaires, dépendance mobile déclarée
Ces 382 tests ne tournaient que lancés à la main, donc jamais. « make test »
demande une base de données et plusieurs minutes ; ceux-ci lisent le code et
exécutent les fragments de shell générés, sudo, pgrep et pkill bouchonnés, en
une dizaine de secondes. Deux cibles : test_unit, et test_unit_file pour la
boucle d'écriture.

La dépendance à mobile/erplibre_home_mobile est DITE plutôt que supposée : le
lanceur l'annonce présente ou absente, et les tests du vrai transfert se
déclarent ignorés — avec la commande qui manque — au lieu de passer en silence.
Vérifié dans les trois états : dépôt absent, présent non compilé, compilé.

Au passage, compile_and_run.sh vérifie le transfert des dépôts, comme
l'installation d'une VM et par le même script.

--- EN ---

These 382 tests only ran when invoked by hand, so never. "make test" wants a
database and several minutes; these read the code and run the generated shell
fragments with sudo, pgrep and pkill stubbed, in about ten seconds. Two targets:
test_unit, and test_unit_file for the writing loop.

The dependency on mobile/erplibre_home_mobile is STATED rather than assumed: the
runner announces it present or absent, and the real-transfer tests declare
themselves skipped — naming the missing command — instead of passing quietly.
Checked in all three states: repo absent, present but unbuilt, built.

Along the way, compile_and_run.sh verifies the repo transfer, like a VM install
and through the same script.

Assisted-by: Claude Opus 5
2026-08-23 02:11:50 -04:00
95d71df204 [FIX] systemd : lancer run.sh par bash, contre les echecs 203/EXEC
erplibre.service mourait en 3 ms sur openSUSE s390x, « status=203/EXEC »,
sans jamais entrer dans le script. Ce code ne dit pas que run.sh a
echoue : il dit que systemd n'a pas pu l'EXECUTER.

Quatre causes le produisent — bit x absent, shebang qui ne resout pas,
/home monte noexec, SELinux refusant l'execve (Leap 16 est passe a
SELinux). Les distinguer demande un acces a la machine ; les traiter
ensemble ne le demande pas.

Passe a « /bin/bash run.sh », le fichier n'est plus qu'une donnee lue :
noexec et SELinux ne portent que sur l'execve, et le bit x devient sans
objet. Mesure : un script en 644 refuse en direct, execute par bash.

Les trois generateurs ecrivaient la meme ligne, les trois sont corriges.
Le venv n'y est pour rien — odoo_bin.sh l'active deja, et c'est celui
d'Odoo, pas celui des outils.

--- EN ---

erplibre.service died in 3 ms on openSUSE s390x, "status=203/EXEC",
never entering the script. That code does not say run.sh failed: it says
systemd could not EXECUTE it.

Four causes produce it — missing x bit, unresolvable shebang, /home
mounted noexec, SELinux denying execve (Leap 16 switched to SELinux).
Telling them apart needs access to the machine; handling them together
does not.

Run as "/bin/bash run.sh", the file is merely data being read: noexec
and SELinux only cover execve, and the x bit becomes moot. Measured: a
644 script refused directly, executed fine through bash.

All three generators wrote the same line; all three are fixed. The venv
is not involved — odoo_bin.sh already activates it, and it is Odoo's,
not the tooling one.

Assisted-by: Claude Opus 5
(cherry picked from commit 6f8cee84b72fd016fa188d204f280b011594627c)
2026-08-23 02:11:50 -04:00
4e0596721c [FIX] filestore: five defects a real repair session brought out
Deduplicating by file was right for COUNTING and wrong for DELETING:
twenty-two rows shared two files, so the purge only ever offered one at
a time and had to be replayed. It now gathers every row whose own field
is dead, and never a live row sharing the same file.

"root" held the root of all filestores instead of this database's
directory, so tidying looked for a nested folder at
<data_dir>/filestore/filestore and answered "nothing to tidy" in front
of 1168 stranded files. It now finds 112 to move up and 1056 duplicates.

limit=0 means "no cap" everywhere else, but the slice [:0] is empty:
"Show every entry" printed "… 3 more" and showed none of them.

The report was captured once, so after a purge it replayed the state
from before -- one then purged rows already gone, believing the work
unfinished. A repair now says whether it did something, and the report
is re-read when it did.

"DELETE 0" was announced as a success. The count now comes from what
PostgreSQL said, and saying nothing is not the same as deleting nothing.

--- FR ---

Dédupliquer par fichier était juste pour COMPTER et faux pour EFFACER :
vingt-deux lignes partageaient deux fichiers, la purge n'en offrait
qu'une à la fois. Elle prend maintenant toutes les lignes dont le champ
est mort, et jamais une ligne vivante partageant le même fichier.

« root » portait la racine de tous les filestores au lieu du dossier de
la base : le rangement cherchait à <data_dir>/filestore/filestore et
répondait « rien à ranger » devant 1168 fichiers échoués. Il en trouve
112 à remonter et 1056 doublons.

limit=0 veut dire « tout » partout ailleurs, mais [:0] est vide : « Tout
afficher » annonçait « … 3 de plus » sans en montrer un seul.

Le rapport n'était lu qu'une fois : après une purge il rejouait l'état
d'avant, et l'on repurgeait des lignes déjà effacées. Une réparation dit
maintenant si elle a fait quelque chose, et le rapport est relu alors.

« DELETE 0 » passait pour un succès. Le compte vient de ce que
PostgreSQL a annoncé, et ne rien dire n'est pas ne rien supprimer.

Assisted-by: Claude Opus 5
2026-08-23 02:09:59 -04:00
1bc2b50ca8 [ADD] filestore: say if the record still exists, and offer the cleanups
A lost image on a deleted task is not a loss -- nobody will ever look
for it. On a LIVING task it is one, and it is the only one worth
regretting. The report now says which: project.task #15 and
calendar.event #1 both still exist, so those two really are gone.

Three states, not two: "could not check" must not read as "it is gone",
or a real loss gets filed as a false alarm.

Two repairs sit in the follow-up menu. Purging rows whose field no
longer exists deletes by ID, never by a rebuilt domain -- replaying the
reasoning in SQL would open the door to deleting more than was shown.
Tidying the nested filestore moves up what is missing and deletes pure
duplicates, never overwriting a file already in place.

--- FR ---

Une image perdue sur une tâche supprimée n'est pas une perte : personne
ne la cherchera. Sur une tâche VIVANTE, c'en est une, et la seule à
regretter. Le rapport le dit : project.task #15 et calendar.event #1
existent encore, ces deux-là sont bien perdues.

Trois états, pas deux : « pas pu vérifier » ne doit pas se lire « a
disparu », sans quoi une vraie perte passe pour une fausse alerte.

Deux réparations dans le menu de suite. La purge efface par IDENTIFIANT,
jamais par un domaine reconstruit -- rejouer le raisonnement en SQL
ouvrirait la porte à effacer plus que ce qui a été montré. Le rangement
remonte ce qui manque et supprime les doublons purs, sans jamais
écraser un fichier déjà en place.

Assisted-by: Claude Opus 5
2026-08-23 02:09:59 -04:00
d6a088e926 [ADD] db_restore: check the filestore landed, and open the tool from the menu
Odoo's shutil.move renames when the destination is absent and NESTS when
it exists, so a leftover filestore/<db>/ sends a whole backup into
filestore/<db>/filestore/, where Odoo never looks. That happened once
here and the clone copied it into all seven databases of the chain --
1168 files, 133 MB each, and nothing said a word.

The check runs after a real restore only. A clone copies its source as
it stands, faults included: checking the mirror would say the same thing
twice, and in the wrong place. It warns and names the fix rather than
aborting -- the database is restored and usable, it is the layout that
is wrong.

--- FR ---

Le shutil.move d'Odoo renomme quand la destination est absente et
IMBRIQUE quand elle existe : un filestore/<base>/ resté là envoie toute
une sauvegarde dans filestore/<base>/filestore/, où Odoo ne regarde
jamais. C'est arrivé une fois ici et le clone l'a recopié dans les sept
bases de la chaîne -- 1168 fichiers, 133 Mo chacune, sans un mot.

Le contrôle ne suit qu'une vraie restauration. Un clone recopie sa
source telle quelle, défauts compris : contrôler le miroir dirait deux
fois la même chose, au mauvais endroit. Il avertit et nomme la
correction plutôt que d'interrompre -- la base est restaurée et
utilisable, c'est la disposition qui cloche.

Assisted-by: Claude Opus 5
2026-08-23 02:09:59 -04:00
4b092c04d2 [ADD] analyse: offer to install the suggested modules that are ready
After the report, only the "available" ones are offered — an unknown
module is not in the addons path and an uninstallable one has a broken
dependency, so listing them would buy three failures. How many were
left out is stated, else the count would look like a bug.

This is the only write in the Analyse menu, so its header no longer
claims otherwise. Three guards: the checkout must match the database
version (an Odoo 18 run against a 12 rewrites it before failing), both
questions default to no, and a rejected token is always shown.

--- FR ---

Après le rapport, seuls les « available » sont proposés — un module
inconnu n'est pas dans le chemin des addons, un cassé a une dépendance
morte : les lister achèterait trois échecs. Le nombre d'écartés est dit,
sinon l'écart de comptage passerait pour un bogue.

C'est la seule écriture du menu Analyse, dont l'en-tête ne prétend donc
plus le contraire. Trois garde-fous : le checkout doit être sur la
version de la base (un Odoo 18 lancé sur une 12 la réécrit avant
d'échouer), les deux questions valent non par défaut, et un jeton
refusé est toujours montré.

Assisted-by: Claude Opus 5
2026-08-23 02:09:59 -04:00
97d2ebec65 [ADD] analyse: modules a database lacks vs the default package
image_db.py --check_addons_exist asks whether a module is on DISK.
Nothing asked whether a given DATABASE has it. After six migration
steps, that is the question: the 18 instance grown from 12 has only 4
of the 15 modules odoo18.0_base ships.

Five verdicts, not one "missing": an available module installs, an
unknown one needs the addons path repaired first, an uninstallable one
has a broken dependency no install will work around.

shortdesc is varchar up to 15 and jsonb after, and step databases of
both shapes appear in one session — so the column type is read, never
assumed.

--- FR ---

image_db.py --check_addons_exist demande si un module est sur le
DISQUE. Personne ne demandait si une BASE donnée l'a. Après six paliers,
c'est pourtant la question : l'instance 18 issue de la 12 n'a que 4 des
15 modules d'odoo18.0_base.

Cinq verdicts, pas un « manquant » : un module disponible s'installe, un
inconnu exige d'abord de réparer le chemin des addons, un cassé a une
dépendance qu'aucune installation ne contournera.

shortdesc est varchar jusqu'en 15 et jsonb ensuite, et les deux formes
se présentent dans la même session — le type est lu, jamais supposé.

Assisted-by: Claude Opus 5
2026-08-23 02:09:59 -04:00
a2cedfa64b [ADD] analyse: diff each website copy against the view it shadows
The analysis counted 62 website copies and pointed at other tools to judge
them. But the comparison that matters for a copy needs no registry at all:
both arches are in the database, paired by key — the very pairing Odoo makes.
So it works where the module-source comparison is refused, on a database whose
version differs from the checkout, and on a backup zip.

On the 12.0 database at hand, where the other comparison cannot run: 47 of the
62 copies have a twin, all 47 compared in under a second, 12 differ. The other
35 are byte-for-byte identical to their module view — they carry no
customization at all, which is what decides whether neutralizing costs
anything.

The fields are named as the module comparison names them, so the full-screen
browser and the text report work without knowing which comparison produced the
data. A copy without a twin is left alone: it is a page made in the editor,
with nothing to compare against.

Checked on that database and on a real backup; 8 tests, including that
re-indentation alone is not a difference and that a missing arch yields no
verdict rather than « identical ».

--- FR ---

L'analyse comptait 62 copies de site web et renvoyait vers d'autres outils
pour les juger. Or la comparaison qui compte pour une copie n'a besoin d'aucun
registre : les deux arch sont dans la base, appariées par la clé — exactement
l'appariement que fait Odoo. Elle marche donc là où celle avec la source du
module est refusée : une base dont la version diffère du checkout, et une
sauvegarde zip.

Sur la base 12.0 en cours, où l'autre comparaison ne peut pas tourner : 47 des
62 copies ont une jumelle, les 47 comparées en moins d'une seconde, 12
diffèrent. Les 35 autres sont identiques octet pour octet à leur vue de module
— elles ne portent aucune personnalisation, ce qui décide si les neutraliser
coûte quelque chose.

Les champs portent les noms de la comparaison avec le module, donc l'écran de
navigation et le rapport texte marchent sans savoir laquelle des deux a
produit la donnée. Une copie sans jumelle est laissée telle quelle : c'est une
page faite dans l'éditeur, il n'y a rien à quoi la comparer.

Vérifié sur cette base et sur une vraie sauvegarde ; 8 tests, dont qu'une
ré-indentation seule n'est pas un écart et qu'une arch absente ne rend aucun
verdict plutôt que « identique ».

Assisted-by: Claude Opus 5
2026-08-23 02:09:59 -04:00
c54bfb7974 [ADD] qemu : régler le mode CPU, les écrans et le réseau d'une VM
Le formulaire matériel ne réglait que vCPU, RAM, 3D et démarrage. Trois
manques : le mode CPU, qui décide si on peut virtualiser DANS la VM, les
écrans du virtio-gpu, et le réseau — dont le passage au pont, seul moyen
d'exposer la VM sur le LAN.

La vram n'est pas offerte : domxml-to-native prouve que QEMU ne la reçoit
jamais sur un virtio-gpu, seul max_outputs y arrive.

Vérifié sur un domaine jetable et un pont d'essai : max_outputs=2, -cpu
host, vnet sur le pont, MAC et emplacement PCI conservés. 486 tests.

--- EN ---

The hardware form only set vCPU, RAM, 3D and autostart. Three gaps: the CPU
mode, which decides whether one can virtualize INSIDE the VM, the virtio-GPU
screens, and the network — including the switch to a bridge, the only way to
put the VM on the LAN.

vram is not offered: domxml-to-native proves QEMU never receives it on a
virtio-GPU, only max_outputs gets through.

Verified on a throwaway domain and a test bridge: max_outputs=2, -cpu host,
vnet on the bridge, MAC and PCI slot preserved. 486 tests.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
a55b03e199 [ADD] qemu : prendre le GPU de l'hôte, et régler le matériel des VM
Une VM graphique sans accélération rend tout par le processeur : le bureau,
et l'émulateur Android qui tourne dedans — 32 % d'images en retard, mesuré.
Le déploiement prend donc le GPU de l'hôte dès qu'un nœud de rendu existe.

Une VM déjà installée n'avait aucune voie : ni vCPU, ni RAM, ni 3D. Le menu
d'état les règle maintenant, pendant qu'elle est éteinte — le seul moment où
libvirt les lit.

Trois pièges du terrain : « --memory N » ne touche que le ballon, l'ajout
d'egl-headless n'est pas idempotent, et son retrait sans cible emporte la
console VNC. Vérifié sur un domaine jetable, 459 tests verts.

--- EN ---

A graphical VM without acceleration renders everything on the CPU: the
desktop, and the Android emulator inside it — 32 % janky frames, measured.
The deployment now takes the host GPU as soon as a render node exists.

An installed VM had no path at all: no vCPU, no RAM, no 3D. The state menu
now sets them while the VM is shut off — the only moment libvirt reads them.

Three field traps: "--memory N" only moves the balloon, adding egl-headless
is not idempotent, and removing it untargeted takes the VNC console with it.
Verified on a throwaway domain, 459 tests green.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
a98d6ec9cd [FIX] script todo: régler l'écran de l'émulateur au lancement, densité comprise
Écrire hw.lcd.* dans le config.ini de l'AVD ne servait à rien : l'émulateur
réécrit ce fichier depuis le profil du téléphone au premier démarrage, et l'AVD
repartait en 1080x2400 densité 420 — quatre fois les pixels voulus. Constaté sur
la VM, où le réglage était censé s'appliquer depuis des semaines.

La taille passe donc au lancement, et la DENSITÉ avec elle. C'est ce point qui
surprend, et il est mesuré sur une charge identique : 540x1140 en densité 420 est
PIRE que le plein écran — 81 ms de médiane contre 40, 57 % d'images en retard
contre 37, tout étant rendu énorme. En densité 240 : 38 ms, 32 %, et le 99e
centile tombe de 950 ms à 250.

« -no-snapshot-save » vient avec : ce menu propose de tuer l'émulateur par
pkill, et le lancement suivant mourait alors sur « A snapshot operation is
pending ». Vérifié après un pkill : plus aucun FATAL.

--- EN ---

Writing hw.lcd.* into the AVD's config.ini did nothing: the emulator rewrites
that file from the phone profile on first boot, and the AVD came back at
1080x2400 density 420 — four times the intended pixels. Found on the VM, where
the setting was supposed to have applied for weeks.

The size therefore moves to launch time, and the DENSITY with it. That is the
surprising part, measured on an identical workload: 540x1140 at density 420 is
WORSE than the full screen — 81 ms median against 40, 57 % janky frames against
37, everything rendered huge. At density 240: 38 ms, 32 %, and the 99th
percentile drops from 950 ms to 250.

"-no-snapshot-save" comes along: this menu offers to kill the emulator with
pkill, and the next start then died on "A snapshot operation is pending".
Checked after a pkill: no FATAL left.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
2c181c60d6 [IMP] script todo: RAM utilisée et uptime dans les infos avancées d'une VM
« RAM » disait l'allocation. Elle dit maintenant l'usage, dans la même colonne :
« 4.7G/32G ». Sur un hyperviseur, savoir qu'une VM de 32 Go n'en occupe que 4,7
décide s'il reste de la place pour la suivante — l'allocation seule ne le dit
pas. La formule est « available - usable » de virsh dommemstat, calibrée contre
le « free » de deux VM : 1186 contre 1216 Mo, et 4831 contre 4838. Et la période
de collecte est posée d'abord, sans quoi le ballon ne rafraîchit rien — une VM
qui occupait 4,8 Go en annonçait 490 Mo.

L'uptime vient de l'âge du processus QEMU : libvirt ne l'expose ni dans dominfo,
ni dans domstats, ni par l'agent, mais ce processus est né avec le domaine. Les
colonnes ont été resserrées pour que la ligne tienne en 80 caractères avec le
nom entier de la VM.

--- EN ---

"RAM" showed the allocation. It now shows the use, in the same column:
"4.7G/32G". On a hypervisor, knowing that a 32 GB VM only occupies 4.7 decides
whether there is room for the next one — the allocation alone does not say. The
formula is virsh dommemstat's "available - usable", calibrated against the "free"
of two VMs: 1186 against 1216 MB, and 4831 against 4838. And the collection
period is set first, without which the balloon refreshes nothing — a VM using
4.8 GB reported 490 MB.

Uptime comes from the age of the QEMU process: libvirt exposes it neither in
dominfo, nor domstats, nor through the agent, but that process was born with the
domain. Columns were tightened so the line fits in 80 characters with the VM's
full name.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
24531f85ff [FIX] script mobile: transférer les dépôts ERPLibre dans l'APK, et le vérifier
Le contournement a vécu : les dépôts n'étaient plus embarqués du tout, l'APK
était refusé pour ses 123 678 entrées quand un ZIP en tient 65 535. Ils entrent
désormais en packs — tranches de 4 Mo et un index par dépôt disant où trouver
chaque fichier — ce qui ramène le compte à 391 entrées sans rien perdre du
contenu. Le côté application est dans le dépôt mobile ; ce commit porte la
vérification et retire le contournement.

Mesuré sur une VM : 139 dépôts, 116 156 fichiers, APK de 282 Mo à 3 002 entrées,
et 20 fichiers relus depuis les packs identiques octet pour octet à leur source.
L'installation le vérifie et échoue sinon : une application qui ne porte pas le
code qu'elle doit montrer n'est pas celle demandée.

--- EN ---

The stopgap has served its time: the repositories were not embedded at all, and
the APK was refused for its 123,678 entries where a ZIP holds 65,535. They now
enter as packs — 4 MB slices and one index per repository saying where each file
lives — which brings the count to 391 entries without losing any content. The
app side lives in the mobile repository; this commit carries the verification
and drops the workaround.

Measured on a VM: 139 repositories, 116,156 files, a 282 MB APK with 3,002
entries, and 20 files read back from the packs identical byte for byte to their
source. The install verifies it and fails otherwise: an app that does not carry
the code it must show is not the one that was asked for.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
7452c581db [ADD] script todo: ouvrir l'écran d'une VM avec virt-viewer
C'est la voie la plus courte : virt-viewer parle à libvirt par « qemu+ssh », lit
le port de l'écran par libvirt et monte SON tunnel — aucun « ssh -L » à tenir
ouvert, rien à deviner. Le menu tunnel le propose donc en cinquième choix.

La seule question qui compte est celle de l'affichage, et c'est l'environnement
qui tranche, pas une question de plus. Un affichage présent (poste, ou ssh -X) :
virt-viewer est installé s'il manque — apt, dnf, pacman ou zypper — puis lancé
détaché. Aucun affichage : la commande est donnée pour le poste, et rien n'est
installé sur une machine sans écran.

--- EN ---

The shortest path: virt-viewer talks to libvirt over "qemu+ssh", reads the
display port from libvirt and builds its OWN tunnel — no "ssh -L" to keep open,
nothing to guess. The tunnel menu offers it as a fifth choice.

The only question that matters is the display, and the environment answers it
rather than one more prompt. A display present (workstation, or ssh -X):
virt-viewer gets installed if missing — apt, dnf, pacman or zypper — then
launched detached. No display: the command is handed over for the workstation,
and nothing is installed on a screenless machine.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
49bc354d25 [FIX] script todo: démarrer le bureau, pas seulement l'activer
Une VM graphique restait sur une console texte jusqu'au premier redémarrage.
GNOME installé, gdm3 installé, graphical.target par défaut, lien
display-manager.service posé par le paquet — et rien à l'écran. Deux causes
superposées, mesurées sur erplibre-ubuntu-2604-gnome :

graphical.target était DÉJÀ atteinte quand le paquet est arrivé, et une cible
active ne rattrape pas un service ajouté après coup. Et « systemctl enable gdm »
rend 0 sans rien faire sur Debian et Ubuntu : l'unité n'a pas de « WantedBy »,
seulement l'alias que le paquet pose lui-même.

Le bureau est donc démarré, avec repli sur le service de la saveur, et l'échec
se dit au lieu de se taire. Vérifié : bureau arrêté puis fragment rejoué ->
gnome-shell revient, écran de connexion GDM à l'image.

--- EN ---

A graphical VM stayed on a text console until its first reboot. GNOME
installed, gdm3 installed, graphical.target the default, the
display-manager.service alias in place by the package — and nothing on screen.
Two causes stacked, measured on erplibre-ubuntu-2604-gnome:

graphical.target had ALREADY been reached when the package arrived, and an
active target does not pick up a service added afterwards. And "systemctl enable
gdm" returns 0 doing nothing on Debian and Ubuntu: the unit has no "WantedBy",
only the alias the package installs itself.

The desktop is therefore started, with a fallback to the flavour's service, and
a failure says so instead of staying quiet. Verified: desktop stopped then the
fragment replayed -> gnome-shell back, GDM greeter on screen.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
1785a2019d [FIX] script todo: nommer les outils qu'une VM sans ERPLibre ne peut pas poser
Les outils de la phase « après » vivent DANS le dépôt : la compilation mobile,
l'AVD, et maintenant le script Forgejo. Sur une VM « bureau seul », sans clone,
ils n'existent pas — et ils étaient écartés en silence. Une case cochée passait
donc pour honorée.

La commande le dit désormais, en nommant les outils concernés. Trois lignes qui
évitent de chercher pourquoi la forge demandée n'est nulle part.

--- EN ---

The "after" phase tools live IN the repository: the mobile build, the AVD, and
now the Forgejo script. On a desktop-only VM, with no clone, they do not exist —
and they were dropped in silence. A ticked checkbox therefore passed for
honoured.

The command now says so, naming the tools concerned. Three lines that save
hunting for why the requested forge is nowhere to be found.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
952c92b52a [ADD] script forgejo: installer une forge git en option cochable
Une case au déploiement, et une forge git auto-hébergée répond sur le port 3000,
git par SSH sur 2222. Le travail vit dans un script dédié, appelable seul sur
une machine existante : une seule autorité pour les deux usages.

Le binaire officiel est statique, donc le même fichier sert apt, dnf, pacman et
zypper — c'est ce qui rend l'option portable sans une branche par distribution.
Les architectures suivent l'amont (amd64, arm64, arm-6) ; la case se grise sur
s390x plutôt que de poser un binaire inexécutable. Les quatre secrets sont
écrits par le script : sans oauth2.JWT_SECRET, Forgejo tente de les persister
lui-même et boucle sur un app.ini qu'il n'a pas le droit d'écrire.

Vérifié sur une VM : somme de contrôle validée, service actif, API qui répond,
dépôt créé puis cloné par git, et relance en 1,5 s sans rien réécrire.

--- EN ---

One checkbox at deploy time, and a self-hosted git forge answers on port 3000,
git over SSH on 2222. The work lives in a dedicated script, callable on its own
for an existing machine: one authority for both uses.

The official binary is static, so the same file serves apt, dnf, pacman and
zypper — that is what makes the option portable without a branch per
distribution. Architectures follow upstream (amd64, arm64, arm-6); the checkbox
greys out on s390x rather than dropping a binary that cannot run. The script
writes all four secrets itself: without oauth2.JWT_SECRET, Forgejo tries to
persist them and loops on an app.ini it is not allowed to write.

Verified on a VM: checksum validated, service active, API answering, a repo
created then cloned over git, and a replay in 1.5 s rewriting nothing.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
69431e17c9 [FIX] script todo: configurer le projet PyCharm avec le venv, et réessayer
Deux défauts empêchaient le .idea d'exister. La configuration d'abord :
« make pycharm_configure » lance le script avec le python SYSTÈME, qui n'a pas
xmltodict — « ModuleNotFoundError », mesuré. update_env_version.pycharm_update()
l'appelle depuis .venv.erplibre ; la cible make et l'étape font désormais pareil.

L'ouverture ensuite : la première tentative sur un dépôt neuf peut n'écrire
aucun .idea, son configurateur d'interpréteur plantant sur « homeDir is null »,
là où la suivante l'écrit en 25 s — constaté sur deux VM. L'étape retente donc
une fois, en gardant les deux journaux. Vérifié sur erplibre-ubuntu-2604-gnome,
caches effacés : erplibre.iml, misc.xml, modules.xml, vcs.xml, et 0 processus
survivant.

--- EN ---

Two defects kept .idea from existing. The configuration first: "make
pycharm_configure" runs the script with the SYSTEM python, which lacks
xmltodict — "ModuleNotFoundError", measured. update_env_version.pycharm_update()
calls it from .venv.erplibre; the make target and the step now do the same.

The open next: the first attempt on a fresh repo can write no .idea at all, its
interpreter configurator dying on "homeDir is null", where the next one writes
it in 25 s — seen on two VMs. The step therefore retries once, keeping both
logs. Verified on erplibre-ubuntu-2604-gnome with caches wiped: erplibre.iml,
misc.xml, modules.xml, vcs.xml, and 0 surviving processes.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
c4d8536509 [FIX] script todo: ouvrir PyCharm après l'installation, pas avant
« ⚠ pas de .idea » à chaque installation : PyCharm ouvrait un dépôt cloné mais
pas installé, son configurateur d'interpréteur Python échouait faute de venv, et
il renonçait avant d'écrire quoi que ce soit. Le même appel sur un dépôt
installé écrit erplibre.iml, misc.xml, modules.xml et vcs.xml en cinq minutes —
mesuré sur la VM.

L'ouverture passe donc après le make, et « make pycharm_configure » la suit :
pycharm_update() s'était déjà exécuté pendant l'installation, quand il n'y avait
rien à configurer. Le groupe rend toujours 0 — un bonus ne rougit pas une VM —
et la phase mobile, qui porte le verdict, reste après lui.

--- EN ---

"⚠ no .idea" on every install: PyCharm was opening a repo that was cloned but
not installed, its Python interpreter configurator failed for lack of a venv,
and it gave up before writing anything. The same call on an installed repo
writes erplibre.iml, misc.xml, modules.xml and vcs.xml in five minutes —
measured on the VM.

The open therefore moves after the make, with "make pycharm_configure" behind
it: pycharm_update() had already run during the install, when there was nothing
to configure. The group always returns 0 — a bonus does not redden a VM — and
the mobile phase, which carries the verdict, still comes after it.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
72b19568af [IMP] script todo: ne pas retélécharger un IDE déjà installé
Rejouer une installation est le cas normal — une qui est morte, un outil ajouté
après coup — et le téléchargement en est la partie longue : environ cinq
minutes pour PyCharm, autant pour Android Studio, à chaque fois. Les deux
étapes vérifient donc /opt avant de sortir curl.

Mesuré sur erplibre-ubuntu-2604-gnome, IDE déjà posés : les deux étapes passent
de dix minutes à 0,094 s au total. Le reste rejoue quand même — lanceur, alias,
raccourci de bureau — il est idempotent et bon marché. Un test vérifie que la
garde n'avale pas l'échec du cas où il faut bel et bien télécharger.

--- EN ---

Replaying an install is the normal case — one that died, a tool added later —
and the download is the long part: about five minutes for PyCharm, as much for
Android Studio, every time. Both steps now check /opt before reaching for curl.

Measured on erplibre-ubuntu-2604-gnome with both IDEs already in place: the two
steps drop from ten minutes to 0.094 s total. The rest still replays — launcher,
alias, desktop entry — it is idempotent and cheap. A test checks the guard does
not swallow the failure of the case where downloading is actually needed.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
087ce3effb [FIX] script todo: viser l'IDE par son nom, pas par la ligne de commande
Le filet de fermeture de PyCharm cherchait « /opt/pycharm » dans les lignes de
commande. Or le script d'installation est passé en argument à ssh, et il
contient ce chemin : le pkill a tué la session ssh qui portait une installation
en cours. Elle est morte sans marqueur de sortie, et le tableau de bord a
montré un sablier pendant 54 minutes. Exécuter la suite de tests suffisait à
déclencher le défaut, puisqu'un test rejoue l'étape.

Le filet vise désormais les NOMS de processus, bornés au compte courant.
Mesuré dans la VM : par nom, 3 processus réels et aucun faux ; par ligne de
commande, 4 — le ssh compris. Un test plante un témoin nommé « sleep » dont la
ligne contient le chemin de l'IDE, et les tests bouchonnent pgrep et pkill.

--- EN ---

PyCharm's closing net looked for "/opt/pycharm" in command lines. But the
install script is passed to ssh as an argument, and it contains that path: the
pkill killed the ssh session carrying a running install. It died with no exit
marker, and the dashboard showed an hourglass for 54 minutes. Running the test
suite was enough to trigger it, since one test replays the step.

The net now targets process NAMES, scoped to the current account. Measured in
the VM: by name, 3 real processes and no false ones; by command line, 4 — the
ssh included. A test plants a witness named "sleep" whose command line holds
the IDE path, and the tests stub pgrep and pkill.

Assisted-by: Claude Opus 5
2026-08-23 02:07:42 -04:00
aa94e460ec [FIX] script todo: ne plus empaqueter les dépôts du manifeste dans l'APK
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
2026-08-23 02:07:42 -04:00
b19713e38f [FIX] script todo: ne plus laisser PyCharm manger la compilation mobile
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
2026-08-23 02:07:42 -04:00
8f5a439148 [FIX] script todo: ne plus annoncer un émulateur qui n'a pas démarré
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
2026-08-23 02:07:42 -04:00
91ff4388e7 [FIX] script todo: rendre la console de l'hyperviseur atteignable
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
2026-08-23 02:07:42 -04:00
13e8b1bd2d [ADD] script todo: lancer l'émulateur Android et son tunnel adb
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
2026-08-23 02:07:42 -04:00
7a65568098 [ADD] todo qemu: offer the adb tunnel for scrcpy, X11-free
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
2026-08-23 02:07:42 -04:00
1dcd19b02f [FIX] todo qemu: name a GPU mode the emulator still accepts
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
2026-08-23 02:07:42 -04:00
ef57e1a097 [IMP] todo qemu: shrink the emulator screen so a remote display keeps up
"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
2026-08-23 02:07:42 -04:00
07b4c81207 [FIX] todo qemu: find the APK where AGP writes it, and install it as it is
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
2026-08-23 02:07:42 -04:00
9ad2048bbb [FIX] todo qemu: the printed emulator command could not run as printed
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
2026-08-23 02:07:42 -04:00
f3be40d74d [ADD] todo qemu: add an Android emulator, and fix what the real run exposed
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
2026-08-23 02:07:42 -04:00
1da54d0ba5 [IMP] todo qemu: let Android Studio find the SDK the build already installed
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
2026-08-23 02:07:42 -04:00
5150cfde9c [ADD] todo qemu: build and test the mobile app, and fail the VM if it breaks
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
2026-08-23 02:07:42 -04:00