[FIX] install Arch (pacman/base-devel) + attente fin cloud-init (Fedora)
Deux échecs d'install ERPLibre sur VM constatés dans les logs : Arch — install_arch_linux.sh utilisait « yay » (helper AUR ABSENT d'une image cloud et qui refuse de tourner en root) et n'installait JAMAIS base-devel : sur une image cloud fraîche rien ne s'installait, donc pas de compilateur C -> pyenv : « no acceptable C compiler found » -> build de Python 3.12 échoué -> pas de venv -> pip/poetry/repo absents. Réécrit avec pacman (dépôts officiels) : base-devel (gcc/make), deps de build pyenv (openssl/zlib/xz/tk/…), PostgreSQL (+ initdb, Arch ne l'initialise pas), Node/npm, deps Odoo en best-effort paquet par paquet (pacman refuse toute la transaction sur un seul nom inconnu). wkhtmltopdf (AUR) : ignoré proprement. Fedora — « Connection closed by remote host » (exit 255) dès le début : « cloud-init status --wait » tournait dans une session SSH UNIQUE tuée quand cloud-init régénère les clés d'hôte et REDÉMARRE sshd au 1er boot. On attend désormais la FIN de cloud-init via des connexions COURTES successives (chaque tentative survit à un redémarrage de sshd), AVANT de lancer l'install — dans les deux chemins : _qemu_wait_ssh (streamé) et _launch_one (détaché/monitoré). On matche sur le TEXTE de « cloud-init status » (done/disabled/error/degraded) car son code de sortie n'est pas fiable selon la version. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
482a914ab8
commit
63e7b6af8c
3 changed files with 147 additions and 62 deletions
|
|
@ -1,68 +1,122 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
# © 2021-2026 TechnoLibre (http://www.technolibre.ca)
|
||||||
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
||||||
|
#
|
||||||
|
# Dépendances système ERPLibre pour Arch Linux (pacman). Réécrit pour les
|
||||||
|
# images cloud Arch : l'ancien script utilisait « yay » (helper AUR ABSENT
|
||||||
|
# d'une image cloud et qui refuse de tourner en root) et n'installait jamais
|
||||||
|
# « base-devel » -> pas de compilateur C -> échec de la compilation de Python
|
||||||
|
# par pyenv. On passe tout par pacman (dépôts officiels) ; l'AUR (wkhtmltopdf)
|
||||||
|
# est best-effort et ne bloque pas.
|
||||||
|
|
||||||
install_package() {
|
. ./env_var.sh
|
||||||
local package_name=$1
|
|
||||||
|
|
||||||
# Check package is already installed
|
EL_USER=${USER}
|
||||||
if pacman -Qs "$package_name" >/dev/null; then
|
|
||||||
echo "$package_name is already installed."
|
|
||||||
else
|
|
||||||
echo "Installation of package $package_name..."
|
|
||||||
yes | yay -S "$package_name"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Odoo installation
|
# pacman résilient : --needed saute ce qui est déjà là, --noconfirm en non
|
||||||
install_package postgis
|
# interactif. On rafraîchit d'abord la base (best-effort : le remote cmd de
|
||||||
install_package postgresql
|
# todo a déjà pu lancer reflector + -Syy).
|
||||||
install_package mariadb
|
PAC="sudo pacman -S --needed --noconfirm"
|
||||||
install_package libev
|
sudo pacman -Sy --noconfirm || true
|
||||||
install_package wkhtmltopdf
|
|
||||||
install_package freetds
|
|
||||||
|
|
||||||
echo "Need password to create symbolic link, create postgres user and install npm :"
|
#--------------------------------------------------
|
||||||
sudo ln -fs /usr/lib/libldap.so /usr/lib/libldap_r.so
|
# Outils de compilation — CRITIQUE (build Python via pyenv, extensions Python)
|
||||||
|
# base-devel fournit gcc, make, patch, pkgconf, fakeroot, etc.
|
||||||
|
#--------------------------------------------------
|
||||||
|
echo -e "\n---- Groupe base-devel (compilateur C, make…) ----"
|
||||||
|
${PAC} base-devel
|
||||||
|
retVal=$?
|
||||||
|
if [[ $retVal -ne 0 ]]; then
|
||||||
|
echo "pacman base-devel installation error."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#--------------------------------------------------
|
||||||
|
# Dépendances de build pyenv (compilation de CPython) + outils de base
|
||||||
|
#--------------------------------------------------
|
||||||
|
echo -e "\n---- Dépendances pyenv (compilation Python) + outils ----"
|
||||||
|
${PAC} git wget curl openssl zlib xz tk bzip2 readline sqlite libffi
|
||||||
|
retVal=$?
|
||||||
|
if [[ $retVal -ne 0 ]]; then
|
||||||
|
echo "pacman pyenv dependencies installation error."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#--------------------------------------------------
|
||||||
|
# PostgreSQL (+ PostGIS) — Arch n'initialise pas le cluster automatiquement
|
||||||
|
#--------------------------------------------------
|
||||||
|
echo -e "\n---- Install PostgreSQL Server ----"
|
||||||
|
${PAC} postgresql
|
||||||
|
retVal=$?
|
||||||
|
if [[ $retVal -ne 0 ]]; then
|
||||||
|
echo "pacman postgresql installation error."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Initialisation du cluster (chemin Arch : /var/lib/postgres/data).
|
||||||
|
if [ ! -f /var/lib/postgres/data/PG_VERSION ]; then
|
||||||
|
echo -e "\n---- Initialisation du cluster PostgreSQL ----"
|
||||||
|
sudo -u postgres initdb --locale=C.UTF-8 --encoding=UTF8 \
|
||||||
|
-D /var/lib/postgres/data || true
|
||||||
|
fi
|
||||||
|
sudo systemctl enable --now postgresql 2>/dev/null || true
|
||||||
|
# PostGIS : optionnel (géospatial), ne bloque pas.
|
||||||
|
${PAC} postgis || echo "PostGIS non installé (optionnel)."
|
||||||
|
|
||||||
|
echo -e "\n---- Creating the ERPLibre PostgreSQL User ----"
|
||||||
sudo su - postgres -c "createuser -s ${EL_USER}" 2>/dev/null || true
|
sudo su - postgres -c "createuser -s ${EL_USER}" 2>/dev/null || true
|
||||||
|
|
||||||
echo -e "\n---- Update NPM ----"
|
#--------------------------------------------------
|
||||||
install_package npm
|
# Dépendances Odoo / ERPLibre (extensions Python, bindings)
|
||||||
|
#--------------------------------------------------
|
||||||
|
echo -e "\n---- Installing arch dependency ----"
|
||||||
|
# best-effort paquet par paquet : pacman refuse TOUTE la transaction si UN
|
||||||
|
# seul nom est inconnu (pas de --skip-unavailable). Ces deps ne sont pas
|
||||||
|
# critiques pour le build Python -> on ne bloque pas sur un nom absent.
|
||||||
|
for _pkg in libxslt libzip libldap libsasl cmake parallel swig portaudio \
|
||||||
|
cups xmlsec freetds libev mariadb-libs shfmt; do
|
||||||
|
${PAC} "${_pkg}" || echo " ${_pkg} : non installé (optionnel), on continue."
|
||||||
|
done
|
||||||
|
# libldap_r : Odoo/python-ldap cherche parfois libldap_r.so (supprimé des
|
||||||
|
# versions récentes d'OpenLDAP) -> lien vers libldap.so.
|
||||||
|
sudo ln -fs /usr/lib/libldap.so /usr/lib/libldap_r.so 2>/dev/null || true
|
||||||
|
|
||||||
sudo npm install npm@latest -g
|
#--------------------------------------------------
|
||||||
|
# Node.js + npm (rtlcss, less)
|
||||||
|
#--------------------------------------------------
|
||||||
|
echo -e "\n---- Installing nodeJS NPM and rtlcss ----"
|
||||||
|
${PAC} nodejs npm
|
||||||
retVal=$?
|
retVal=$?
|
||||||
if [[ $retVal -ne 0 ]]; then
|
if [[ $retVal -ne 0 ]]; then
|
||||||
echo "npm install npm lastest installation error."
|
echo "pacman nodejs installation error."
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
sudo npm install -g rtlcss
|
|
||||||
retVal=$?
|
|
||||||
if [[ $retVal -ne 0 ]]; then
|
|
||||||
echo "npm install rtlcss installation error."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
sudo npm install -g less
|
|
||||||
retVal=$?
|
|
||||||
if [[ $retVal -ne 0 ]]; then
|
|
||||||
echo "npm install less installation error."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
sudo npm install -g rtlcss less || echo "npm rtlcss/less: erreur (optionnel)."
|
||||||
|
|
||||||
echo -e "\n---- Test tool ----"
|
echo -e "\n---- Test tool ----"
|
||||||
npm install
|
npm install || echo "npm install (prettier/plugin-xml): erreur (optionnel)."
|
||||||
retVal=$?
|
|
||||||
if [[ $retVal -ne 0 ]]; then
|
# Pour erplibre_devops.
|
||||||
echo "npm install prettier + plugin-xml installation error."
|
${PAC} sshpass || echo "sshpass non installé (optionnel)."
|
||||||
exit 1
|
|
||||||
|
#--------------------------------------------------
|
||||||
|
# nginx (optionnel)
|
||||||
|
#--------------------------------------------------
|
||||||
|
if [ "${EL_INSTALL_NGINX}" = "True" ]; then
|
||||||
|
echo -e "\n---- Installing nginx ----"
|
||||||
|
${PAC} nginx || echo "nginx: erreur (optionnel)."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# TODO install nginx
|
#--------------------------------------------------
|
||||||
|
# wkhtmltopdf (optionnel) — uniquement dans l'AUR sur Arch (pas de paquet
|
||||||
# ERPLibre installation
|
# officiel). Sur une image cloud sans helper AUR, on saute proprement.
|
||||||
install_package cmake
|
#--------------------------------------------------
|
||||||
install_package parallel
|
if [ "${EL_INSTALL_WKHTMLTOPDF}" = "True" ]; then
|
||||||
install_package tk
|
if ! command -v wkhtmltopdf >/dev/null 2>&1; then
|
||||||
install_package shfmt
|
echo "wkhtmltopdf : disponible seulement via l'AUR sur Arch, ignoré"
|
||||||
|
echo " (installez-le manuellement avec un helper AUR si nécessaire)."
|
||||||
# For erplibre_devops
|
else
|
||||||
install_package sshpass
|
echo -e "\n---- Already installed wkhtml ----"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "\n---- Arch Linux dependency installation done ----"
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,25 @@ def session_dir() -> Path:
|
||||||
def _launch_one(ip: str, remote_cmd: str, log_path: str) -> None:
|
def _launch_one(ip: str, remote_cmd: str, log_path: str) -> None:
|
||||||
"""Lance une install SSH DÉTACHÉE : attend le sshd, exécute, journalise
|
"""Lance une install SSH DÉTACHÉE : attend le sshd, exécute, journalise
|
||||||
la sortie puis écrit le marqueur de fin avec le code de sortie."""
|
la sortie puis écrit le marqueur de fin avec le code de sortie."""
|
||||||
|
# Sonde de disponibilité : on attend que sshd réponde ET que cloud-init
|
||||||
|
# soit TERMINÉ, via des connexions COURTES successives (jusqu'à ~12 min).
|
||||||
|
# Au 1er boot, cloud-init régénère les clés d'hôte et REDÉMARRE sshd : une
|
||||||
|
# session SSH longue (ex. « cloud-init status --wait ») serait alors tuée
|
||||||
|
# (« Connection closed by remote host », exit 255 — cas Fedora). Chaque
|
||||||
|
# itération étant une connexion neuve, un redémarrage de sshd ne casse que
|
||||||
|
# la tentative en cours. On imprime toujours l'état (|| true) pour matcher
|
||||||
|
# sur le TEXTE, « status: running » n'ayant pas de code de sortie fiable.
|
||||||
|
ci_probe = (
|
||||||
|
"if command -v cloud-init >/dev/null 2>&1; then "
|
||||||
|
"cloud-init status 2>/dev/null || true; else echo nocloudinit; fi"
|
||||||
|
)
|
||||||
wrapper = (
|
wrapper = (
|
||||||
# Attente du sshd (jusqu'à ~5 min) pour éviter « Connection refused ».
|
f"for i in $(seq 1 144); do "
|
||||||
f"for i in $(seq 1 60); do "
|
f"st=$(ssh {SSH_OPTS} -o BatchMode=yes erplibre@{ip} "
|
||||||
f"ssh {SSH_OPTS} -o BatchMode=yes erplibre@{ip} true "
|
f"{shlex.quote(ci_probe)} 2>/dev/null); "
|
||||||
f">/dev/null 2>&1 && break; sleep 5; done; "
|
f'case "$st" in '
|
||||||
|
f"*done*|*disabled*|*error*|*degraded*|*nocloudinit*) break;; "
|
||||||
|
f"esac; sleep 5; done; "
|
||||||
f"ssh {SSH_OPTS} erplibre@{ip} {shlex.quote(remote_cmd)} "
|
f"ssh {SSH_OPTS} erplibre@{ip} {shlex.quote(remote_cmd)} "
|
||||||
f"> {shlex.quote(log_path)} 2>&1; "
|
f"> {shlex.quote(log_path)} 2>&1; "
|
||||||
f'echo "{EXIT_MARKER} $?" >> {shlex.quote(log_path)}'
|
f'echo "{EXIT_MARKER} $?" >> {shlex.quote(log_path)}'
|
||||||
|
|
|
||||||
|
|
@ -1677,28 +1677,45 @@ class TODO:
|
||||||
ERPLIBRE_EXTRA_DISK_GB = 5
|
ERPLIBRE_EXTRA_DISK_GB = 5
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _qemu_wait_ssh(ip, user="erplibre", timeout=180):
|
def _qemu_wait_ssh(ip, user="erplibre", timeout=720):
|
||||||
"""Attend que le SSH réponde (le sshd peut mettre du temps à démarrer
|
"""Attend que sshd réponde ET que cloud-init soit TERMINÉ, via des
|
||||||
après le boot, surtout sur Fedora). True si joignable."""
|
connexions COURTES successives. Au 1er boot, cloud-init régénère les
|
||||||
|
clés d'hôte et REDÉMARRE sshd : attendre la fin de cloud-init AVANT de
|
||||||
|
lancer l'install évite qu'une session longue soit tuée (« Connection
|
||||||
|
closed by remote host », exit 255 — cas Fedora). True si prête."""
|
||||||
opts = (
|
opts = (
|
||||||
"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "
|
"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "
|
||||||
"-o ConnectTimeout=8 -o BatchMode=yes"
|
"-o ConnectTimeout=8 -o BatchMode=yes"
|
||||||
)
|
)
|
||||||
|
# On imprime toujours l'état (|| true) pour matcher sur le TEXTE :
|
||||||
|
# « status: running » n'a pas de code de sortie fiable selon la version.
|
||||||
|
probe = (
|
||||||
|
"if command -v cloud-init >/dev/null 2>&1; then "
|
||||||
|
"cloud-init status 2>/dev/null || true; else echo nocloudinit; fi"
|
||||||
|
)
|
||||||
|
ready = ("done", "disabled", "error", "degraded", "nocloudinit")
|
||||||
deadline = time.time() + timeout
|
deadline = time.time() + timeout
|
||||||
|
ssh_up = False
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
try:
|
try:
|
||||||
res = subprocess.run(
|
res = subprocess.run(
|
||||||
f"ssh {opts} {user}@{ip} true",
|
f"ssh {opts} {user}@{ip} {shlex.quote(probe)}",
|
||||||
shell=True,
|
shell=True,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
timeout=15,
|
timeout=20,
|
||||||
|
text=True,
|
||||||
)
|
)
|
||||||
|
out = res.stdout or ""
|
||||||
except (OSError, subprocess.SubprocessError):
|
except (OSError, subprocess.SubprocessError):
|
||||||
res = None
|
out = ""
|
||||||
if res is not None and res.returncode == 0:
|
if out.strip():
|
||||||
|
ssh_up = True # sshd a répondu
|
||||||
|
if any(k in out for k in ready):
|
||||||
return True
|
return True
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
return False
|
# cloud-init pas confirmé fini dans le délai : on tente quand même si
|
||||||
|
# sshd répondait au moins (mieux qu'un abandon silencieux).
|
||||||
|
return ssh_up
|
||||||
|
|
||||||
def _qemu_erplibre_remote_cmd(self, branch):
|
def _qemu_erplibre_remote_cmd(self, branch):
|
||||||
"""Script d'installation ERPLibre exécuté DANS la VM (curl/git/make
|
"""Script d'installation ERPLibre exécuté DANS la VM (curl/git/make
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue