[IMP] install.sh: ask y/N before installing the tooling venv

On a system too old to read TODO, install.sh ran the install without asking,
and the install DELETES an existing .venv.erplibre. It now runs only on a yes
typed in a terminal: o, oui, y or yes, any case. Enter alone means no, and
with no terminal no question is asked and nothing is installed. « aucun
python3 » finally shows when the probe is silent: the || fallback never fired
behind a pipe.
Checked: 16 tests, each answer on a pseudo-terminal; install.sh run in an
isolated copy exits 1 on « n » and installs on « o ».

--- FR ---

[IMP] install.sh : demander o/N avant d'installer le venv d'outillage

Sur un système trop ancien pour lire TODO, install.sh lançait l'installation
sans rien demander, et celle-ci SUPPRIME un .venv.erplibre existant. Elle ne
part plus que sur un oui tapé au terminal : o, oui, y ou yes, toute casse.
Entrée seule vaut non, et sans terminal aucune question n'est posée ni
installation lancée. « aucun python3 » s'affiche enfin quand la sonde est
muette : le repli par || ne se déclenchait jamais derrière un tube.
Vérifié : 16 tests, dont chaque réponse sur un pseudo-terminal ; install.sh
lancé dans une copie isolée sort en 1 sur « n » et installe sur « o ».

Assisted-by: Claude Opus 5.5
This commit is contained in:
Mathieu Benoit 2026-09-25 01:29:53 -04:00
parent a8f7512130
commit aa3f99e746
8 changed files with 129 additions and 14 deletions

View file

@ -162,7 +162,7 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `--bios` is refused on an image with no BIOS boot sector, and says why. Forced there, it gave a VM reported « running » with a silent console — the very failure that flag exists to avoid elsewhere
- The tooling virtual environment `.venv.erplibre` runs Python 3.14.7, independently of the Odoo one (3.12.10 for Odoo 18.0). `install_erplibre.sh` builds it through `install_venv.sh` and `EL_PYTHON_PROVIDER` instead of the system `python3`
- `.venv.erplibre` on an incompatible Python is DELETED and rebuilt; whatever was installed in it by hand goes with it. Odoo's venv is kept when it merely differs in version, and rebuilt only when it is unusable: rebuilding it redoes a whole Poetry install. A directory without `pyvenv.cfg` is never deleted
- `make` and `make todo` run `./todo.sh`, a launcher whose name says a menu opens and not an installation; it hands every argument to `install.sh`, which picks an interpreter able to READ the code before running it: `.venv.erplibre` on the right version, else a recent enough system `python3`, else the install. A system older than `conf/python-erplibre-version` would otherwise stop on a syntax error raised before any guard could name the command to type. TODO then relaunches itself in `.venv.erplibre`, or offers to run `install_erplibre.sh` in a terminal
- `make` and `make todo` run `./todo.sh`, a launcher whose name says a menu opens and not an installation; it hands every argument to `install.sh`, which picks an interpreter able to READ the code before running it: `.venv.erplibre` on the right version, else a recent enough system `python3`, else the install, which runs only on a yes typed in a terminal (`o`, `oui`, `y`, `yes`) since it can delete `.venv.erplibre`. A system older than `conf/python-erplibre-version` would otherwise stop on a syntax error raised before any guard could name the command to type. TODO then relaunches itself in `.venv.erplibre`, or offers to run `install_erplibre.sh` in a terminal
- The production Docker image builds `.venv.erplibre` on Odoo's Python and stops when that Python cannot parse `script/`
- Debian 11 is dropped from the deployment catalogue: its LTS ended, and its security suite is neither served nor archived — the index it still publishes names packages whose pool no longer holds the file, so apt stops before installing git. Debian 13 takes its place, its cloud image always being the latest point release
- Every container image is built on bookworm, whatever the Odoo version. The base is `python:<version>-slim-<suite>`: the interpreter comes from the official image, never from Debian, and the bookworm variants exist down to 3.7.17. The wkhtmltopdf build follows the suite — bullseye's requires libssl1.1, absent from bookworm
@ -187,7 +187,7 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `--bios` est refusé sur une image sans secteur d'amorçage BIOS, et dit pourquoi. Forcé là, il donnait une VM « running » à console muette — la panne même que ce drapeau évite ailleurs
- L'environnement virtuel d'outillage `.venv.erplibre` tourne en Python 3.14.7, indépendamment de celui d'Odoo (3.12.10 pour Odoo 18.0). `install_erplibre.sh` le bâtit par `install_venv.sh` et `EL_PYTHON_PROVIDER` plutôt qu'avec le `python3` du système
- `.venv.erplibre` sur un Python incompatible est SUPPRIMÉ puis rebâti ; ce qu'on y avait posé à la main part avec lui. Le venv d'Odoo est conservé quand seule sa version diffère, et rebâti seulement s'il est hors service : le rebâtir refait une installation Poetry entière. Un répertoire sans `pyvenv.cfg` n'est jamais effacé
- `make` et `make todo` lancent `./todo.sh`, dont le nom annonce un menu et non une installation ; il passe chaque argument à `install.sh`, qui choisit un interpréteur capable de LIRE le code avant de le lancer : `.venv.erplibre` s'il porte la bonne version, sinon le `python3` du système s'il est assez récent, sinon l'installation. Un système plus ancien que `conf/python-erplibre-version` s'arrêterait autrement sur une erreur de syntaxe levée avant qu'aucun garde puisse nommer la commande à taper. TODO se relance ensuite dans `.venv.erplibre`, ou propose de lancer `install_erplibre.sh` en terminal
- `make` et `make todo` lancent `./todo.sh`, dont le nom annonce un menu et non une installation ; il passe chaque argument à `install.sh`, qui choisit un interpréteur capable de LIRE le code avant de le lancer : `.venv.erplibre` s'il porte la bonne version, sinon le `python3` du système s'il est assez récent, sinon l'installation, qui ne part que sur un oui tapé au terminal (`o`, `oui`, `y`, `yes`) puisqu'elle peut supprimer `.venv.erplibre`. Un système plus ancien que `conf/python-erplibre-version` s'arrêterait autrement sur une erreur de syntaxe levée avant qu'aucun garde puisse nommer la commande à taper. TODO se relance ensuite dans `.venv.erplibre`, ou propose de lancer `install_erplibre.sh` en terminal
- L'image Docker de production bâtit `.venv.erplibre` sur le Python d'Odoo et s'arrête quand ce Python ne sait pas lire `script/`
- Debian 11 quitte le catalogue de déploiement : son LTS est terminé, et sa suite de sécurité n'est ni servie ni archivée — l'index qu'elle publie encore nomme des paquets dont le pool ne porte plus le fichier, et apt s'arrête avant d'installer git. Debian 13 la remplace, son image cloud étant toujours le dernier point de version
- Toutes les images de conteneur reposent sur bookworm, quelle que soit la version d'Odoo. La base est `python:<version>-slim-<version Debian>` : l'interpréteur vient de l'image officielle, jamais de Debian, et les variantes bookworm existent jusqu'à 3.7.17. Le build de wkhtmltopdf suit la version — celui de bullseye réclame libssl1.1, absente de bookworm

View file

@ -82,7 +82,7 @@ au [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `--bios` est refusé sur une image sans secteur d'amorçage BIOS, et dit pourquoi. Forcé là, il donnait une VM « running » à console muette — la panne même que ce drapeau évite ailleurs
- L'environnement virtuel d'outillage `.venv.erplibre` tourne en Python 3.14.7, indépendamment de celui d'Odoo (3.12.10 pour Odoo 18.0). `install_erplibre.sh` le bâtit par `install_venv.sh` et `EL_PYTHON_PROVIDER` plutôt qu'avec le `python3` du système
- `.venv.erplibre` sur un Python incompatible est SUPPRIMÉ puis rebâti ; ce qu'on y avait posé à la main part avec lui. Le venv d'Odoo est conservé quand seule sa version diffère, et rebâti seulement s'il est hors service : le rebâtir refait une installation Poetry entière. Un répertoire sans `pyvenv.cfg` n'est jamais effacé
- `make` et `make todo` lancent `./todo.sh`, dont le nom annonce un menu et non une installation ; il passe chaque argument à `install.sh`, qui choisit un interpréteur capable de LIRE le code avant de le lancer : `.venv.erplibre` s'il porte la bonne version, sinon le `python3` du système s'il est assez récent, sinon l'installation. Un système plus ancien que `conf/python-erplibre-version` s'arrêterait autrement sur une erreur de syntaxe levée avant qu'aucun garde puisse nommer la commande à taper. TODO se relance ensuite dans `.venv.erplibre`, ou propose de lancer `install_erplibre.sh` en terminal
- `make` et `make todo` lancent `./todo.sh`, dont le nom annonce un menu et non une installation ; il passe chaque argument à `install.sh`, qui choisit un interpréteur capable de LIRE le code avant de le lancer : `.venv.erplibre` s'il porte la bonne version, sinon le `python3` du système s'il est assez récent, sinon l'installation, qui ne part que sur un oui tapé au terminal (`o`, `oui`, `y`, `yes`) puisqu'elle peut supprimer `.venv.erplibre`. Un système plus ancien que `conf/python-erplibre-version` s'arrêterait autrement sur une erreur de syntaxe levée avant qu'aucun garde puisse nommer la commande à taper. TODO se relance ensuite dans `.venv.erplibre`, ou propose de lancer `install_erplibre.sh` en terminal
- L'image Docker de production bâtit `.venv.erplibre` sur le Python d'Odoo et s'arrête quand ce Python ne sait pas lire `script/`
- Debian 11 quitte le catalogue de déploiement : son LTS est terminé, et sa suite de sécurité n'est ni servie ni archivée — l'index qu'elle publie encore nomme des paquets dont le pool ne porte plus le fichier, et apt s'arrête avant d'installer git. Debian 13 la remplace, son image cloud étant toujours le dernier point de version
- Toutes les images de conteneur reposent sur bookworm, quelle que soit la version d'Odoo. La base est `python:<version>-slim-<version Debian>` : l'interpréteur vient de l'image officielle, jamais de Debian, et les variantes bookworm existent jusqu'à 3.7.17. Le build de wkhtmltopdf suit la version — celui de bullseye réclame libssl1.1, absente de bookworm

View file

@ -82,7 +82,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `--bios` is refused on an image with no BIOS boot sector, and says why. Forced there, it gave a VM reported « running » with a silent console — the very failure that flag exists to avoid elsewhere
- The tooling virtual environment `.venv.erplibre` runs Python 3.14.7, independently of the Odoo one (3.12.10 for Odoo 18.0). `install_erplibre.sh` builds it through `install_venv.sh` and `EL_PYTHON_PROVIDER` instead of the system `python3`
- `.venv.erplibre` on an incompatible Python is DELETED and rebuilt; whatever was installed in it by hand goes with it. Odoo's venv is kept when it merely differs in version, and rebuilt only when it is unusable: rebuilding it redoes a whole Poetry install. A directory without `pyvenv.cfg` is never deleted
- `make` and `make todo` run `./todo.sh`, a launcher whose name says a menu opens and not an installation; it hands every argument to `install.sh`, which picks an interpreter able to READ the code before running it: `.venv.erplibre` on the right version, else a recent enough system `python3`, else the install. A system older than `conf/python-erplibre-version` would otherwise stop on a syntax error raised before any guard could name the command to type. TODO then relaunches itself in `.venv.erplibre`, or offers to run `install_erplibre.sh` in a terminal
- `make` and `make todo` run `./todo.sh`, a launcher whose name says a menu opens and not an installation; it hands every argument to `install.sh`, which picks an interpreter able to READ the code before running it: `.venv.erplibre` on the right version, else a recent enough system `python3`, else the install, which runs only on a yes typed in a terminal (`o`, `oui`, `y`, `yes`) since it can delete `.venv.erplibre`. A system older than `conf/python-erplibre-version` would otherwise stop on a syntax error raised before any guard could name the command to type. TODO then relaunches itself in `.venv.erplibre`, or offers to run `install_erplibre.sh` in a terminal
- The production Docker image builds `.venv.erplibre` on Odoo's Python and stops when that Python cannot parse `script/`
- Debian 11 is dropped from the deployment catalogue: its LTS ended, and its security suite is neither served nor archived — the index it still publishes names packages whose pool no longer holds the file, so apt stops before installing git. Debian 13 takes its place, its cloud image always being the latest point release
- Every container image is built on bookworm, whatever the Odoo version. The base is `python:<version>-slim-<suite>`: the interpreter comes from the official image, never from Debian, and the bookworm variants exist down to 3.7.17. The wkhtmltopdf build follows the suite — bullseye's requires libssl1.1, absent from bookworm

View file

@ -257,8 +257,10 @@ make
`make` and `./install.sh` start TODO through the interpreter that can read it:
`.venv.erplibre` when it carries the right version, otherwise the system
`python3` when it is recent enough, otherwise the install itself — a system
older than `conf/python-erplibre-version` cannot parse the code, so it goes
straight to the install rather than stopping on a syntax error. When the
older than `conf/python-erplibre-version` cannot parse the code, so it offers
the install rather than stopping on a syntax error. The install runs only on a
yes typed in a terminal (`o`, `oui`, `y` or `yes`); Enter alone, or no
terminal, means no. When the
environment is missing but the system Python suffices, TODO offers to run
`./script/install/install_erplibre.sh` (in a terminal) or prints that command. The install builds the environment
through `EL_PYTHON_PROVIDER` (mise or pyenv); an existing `.venv.erplibre` on
@ -270,8 +272,10 @@ by hand goes with it.
`make` et `./install.sh` lancent TODO par l'interpréteur capable de le lire :
`.venv.erplibre` quand il porte la bonne version, sinon le `python3` du système
s'il est assez récent, sinon l'installation elle-même — un système plus ancien
que `conf/python-erplibre-version` ne sait pas analyser le code, donc on va
droit à l'installation plutôt que de s'arrêter sur une erreur de syntaxe. Quand
que `conf/python-erplibre-version` ne sait pas analyser le code, donc
l'installation est proposée plutôt que de s'arrêter sur une erreur de syntaxe.
Elle ne part que sur un oui tapé au terminal (`o`, `oui`, `y` ou `yes`) ;
Entrée seule, ou l'absence de terminal, vaut non. Quand
l'environnement manque mais que le Python du système suffit, TODO propose de
lancer `./script/install/install_erplibre.sh` (dans un terminal) ou affiche
cette commande. L'installation bâtit l'environnement par

View file

@ -129,8 +129,10 @@ make
`make` et `./install.sh` lancent TODO par l'interpréteur capable de le lire :
`.venv.erplibre` quand il porte la bonne version, sinon le `python3` du système
s'il est assez récent, sinon l'installation elle-même — un système plus ancien
que `conf/python-erplibre-version` ne sait pas analyser le code, donc on va
droit à l'installation plutôt que de s'arrêter sur une erreur de syntaxe. Quand
que `conf/python-erplibre-version` ne sait pas analyser le code, donc
l'installation est proposée plutôt que de s'arrêter sur une erreur de syntaxe.
Elle ne part que sur un oui tapé au terminal (`o`, `oui`, `y` ou `yes`) ;
Entrée seule, ou l'absence de terminal, vaut non. Quand
l'environnement manque mais que le Python du système suffit, TODO propose de
lancer `./script/install/install_erplibre.sh` (dans un terminal) ou affiche
cette commande. L'installation bâtit l'environnement par

View file

@ -127,8 +127,10 @@ make
`make` and `./install.sh` start TODO through the interpreter that can read it:
`.venv.erplibre` when it carries the right version, otherwise the system
`python3` when it is recent enough, otherwise the install itself — a system
older than `conf/python-erplibre-version` cannot parse the code, so it goes
straight to the install rather than stopping on a syntax error. When the
older than `conf/python-erplibre-version` cannot parse the code, so it offers
the install rather than stopping on a syntax error. The install runs only on a
yes typed in a terminal (`o`, `oui`, `y` or `yes`); Enter alone, or no
terminal, means no. When the
environment is missing but the system Python suffices, TODO offers to run
`./script/install/install_erplibre.sh` (in a terminal) or prints that command. The install builds the environment
through `EL_PYTHON_PROVIDER` (mise or pyenv); an existing `.venv.erplibre` on

View file

@ -42,6 +42,20 @@ el_assez_recent() {
= "${ATTENDUE}" ]
}
# Vrai sur une réponse affirmative lue au terminal, en français ou en anglais :
# o, oui, y, yes, sans égard à la casse. Le défaut est NON : une frappe sur
# Entrée ne lance pas une installation qui peut détruire un venv. Sans
# terminal sur l'entrée, rien ne peut consentir, et la réponse est non.
el_confirmer() {
[ -t 0 ] || return 1
printf '%s [o/N] ' "$1"
read -r reponse || return 1
case "$(printf '%s' "${reponse}" | tr '[:upper:]' '[:lower:]')" in
o | oui | y | yes) return 0 ;;
*) return 1 ;;
esac
}
PYTHON_VENV="./${VENV}/bin/python"
if [ -n "${VENV}" ] && [ -x "${PYTHON_VENV}" ] \
&& el_assez_recent "$(el_mineure "${PYTHON_VENV}")"; then
@ -64,7 +78,17 @@ if command -v python3 > /dev/null 2>&1 \
exec ./script/todo/todo.py "$@"
fi
SYSTEME="$(el_mineure python3)"
echo "Python ${VOULUE:-du depot} est requis pour lire le code de TODO ;"
echo " ce systeme livre $(el_mineure python3 2> /dev/null || echo 'aucun python3')."
echo " Installation du venv d'outillage, qui le pose :"
echo " ce systeme livre ${SYSTEME:-aucun python3 utilisable}."
echo " L'installation du venv d'outillage le pose :"
echo " ./script/install/install_erplibre.sh"
if [ -d "./${VENV}" ]; then
echo " ${VENV} existant sera SUPPRIME puis rebati, avec ce qui y a ete"
echo " pose a la main."
fi
if ! el_confirmer "Lancer l'installation ?"; then
echo "Installation non lancee : TODO ne demarre pas."
exit 1
fi
exec ./script/todo/source_todo.sh "$@"

View file

@ -0,0 +1,83 @@
#!/usr/bin/env python3
# © 2026 TechnoLibre (http://www.technolibre.ca)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
"""La question qu'install.sh pose avant l'installation du venv d'outillage.
L'installation peut SUPPRIMER un .venv.erplibre existant : elle ne part que
sur un oui explicite. La réponse se donne en français ou en anglais (o, oui,
y, yes, toute casse) ; Entrée seule vaut non. Sans terminal sur l'entrée,
personne ne peut consentir, et la réponse est non sans question posée.
"""
import os
import select
import subprocess
import unittest
from pathlib import Path
RACINE = Path(__file__).resolve().parent.parent
SCRIPT = (
"source <(sed -n '/^el_confirmer()/,/^}/p' install.sh); "
'el_confirmer "Lancer ?"'
)
def repondre_au_terminal(reponse):
"""Lance el_confirmer sur un pseudo-terminal, y tape la réponse.
Rend le code de sortie et ce que le terminal a affiché.
"""
maitre, esclave = os.openpty()
proc = subprocess.Popen(
["bash", "-c", SCRIPT],
cwd=RACINE,
stdin=esclave,
stdout=esclave,
stderr=esclave,
)
os.close(esclave)
os.write(maitre, (reponse + "\n").encode())
code = proc.wait(timeout=10)
lu = b""
while select.select([maitre], [], [], 0.2)[0]:
try:
morceau = os.read(maitre, 4096)
except OSError:
break
if not morceau:
break
lu += morceau
os.close(maitre)
return code, lu.decode(errors="replace")
class TestConfirmer(unittest.TestCase):
def test_oui_en_francais_et_en_anglais(self):
for reponse in ("o", "O", "oui", "Oui", "y", "Y", "yes", "YES"):
with self.subTest(reponse=reponse):
self.assertEqual(repondre_au_terminal(reponse)[0], 0)
def test_entree_seule_et_non_refusent(self):
for reponse in ("", "n", "non", "no", "ouais", "x"):
with self.subTest(reponse=reponse):
self.assertEqual(repondre_au_terminal(reponse)[0], 1)
def test_la_question_annonce_le_defaut(self):
self.assertIn("Lancer ? [o/N]", repondre_au_terminal("n")[1])
def test_sans_terminal_la_reponse_est_non(self):
proc = subprocess.run(
["bash", "-c", SCRIPT],
cwd=RACINE,
input="o\n",
capture_output=True,
text=True,
check=False,
)
self.assertEqual(proc.returncode, 1)
self.assertNotIn("[o/N]", proc.stdout)
if __name__ == "__main__":
unittest.main()