diff --git a/script/install/install_arch_linux.sh b/script/install/install_arch_linux.sh index a350f96..0cfad1d 100755 --- a/script/install/install_arch_linux.sh +++ b/script/install/install_arch_linux.sh @@ -1,68 +1,122 @@ #!/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() { - local package_name=$1 +. ./env_var.sh - # Check package is already installed - 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 -} +EL_USER=${USER} -# Odoo installation -install_package postgis -install_package postgresql -install_package mariadb -install_package libev -install_package wkhtmltopdf -install_package freetds +# pacman résilient : --needed saute ce qui est déjà là, --noconfirm en non +# interactif. On rafraîchit d'abord la base (best-effort : le remote cmd de +# todo a déjà pu lancer reflector + -Syy). +PAC="sudo pacman -S --needed --noconfirm" +sudo pacman -Sy --noconfirm || true -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 -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=$? if [[ $retVal -ne 0 ]]; then - echo "npm install npm lastest 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." + echo "pacman nodejs installation error." exit 1 fi +sudo npm install -g rtlcss less || echo "npm rtlcss/less: erreur (optionnel)." echo -e "\n---- Test tool ----" -npm install -retVal=$? -if [[ $retVal -ne 0 ]]; then - echo "npm install prettier + plugin-xml installation error." - exit 1 +npm install || echo "npm install (prettier/plugin-xml): erreur (optionnel)." + +# Pour erplibre_devops. +${PAC} sshpass || echo "sshpass non installé (optionnel)." + +#-------------------------------------------------- +# nginx (optionnel) +#-------------------------------------------------- +if [ "${EL_INSTALL_NGINX}" = "True" ]; then + echo -e "\n---- Installing nginx ----" + ${PAC} nginx || echo "nginx: erreur (optionnel)." fi -# TODO install nginx - -# ERPLibre installation -install_package cmake -install_package parallel -install_package tk -install_package shfmt - -# For erplibre_devops -install_package sshpass +#-------------------------------------------------- +# wkhtmltopdf (optionnel) — uniquement dans l'AUR sur Arch (pas de paquet +# officiel). Sur une image cloud sans helper AUR, on saute proprement. +#-------------------------------------------------- +if [ "${EL_INSTALL_WKHTMLTOPDF}" = "True" ]; then + if ! command -v wkhtmltopdf >/dev/null 2>&1; then + echo "wkhtmltopdf : disponible seulement via l'AUR sur Arch, ignoré" + echo " (installez-le manuellement avec un helper AUR si nécessaire)." + else + echo -e "\n---- Already installed wkhtml ----" + fi +fi +echo -e "\n---- Arch Linux dependency installation done ----" diff --git a/script/todo/qemu_install_monitor.py b/script/todo/qemu_install_monitor.py index 234c212..5fc18a2 100644 --- a/script/todo/qemu_install_monitor.py +++ b/script/todo/qemu_install_monitor.py @@ -37,11 +37,25 @@ def session_dir() -> Path: 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 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 = ( - # Attente du sshd (jusqu'à ~5 min) pour éviter « Connection refused ». - f"for i in $(seq 1 60); do " - f"ssh {SSH_OPTS} -o BatchMode=yes erplibre@{ip} true " - f">/dev/null 2>&1 && break; sleep 5; done; " + f"for i in $(seq 1 144); do " + f"st=$(ssh {SSH_OPTS} -o BatchMode=yes erplibre@{ip} " + f"{shlex.quote(ci_probe)} 2>/dev/null); " + 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"> {shlex.quote(log_path)} 2>&1; " f'echo "{EXIT_MARKER} $?" >> {shlex.quote(log_path)}' diff --git a/script/todo/todo.py b/script/todo/todo.py index 6ce3fcc..cf07683 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -1677,28 +1677,45 @@ class TODO: ERPLIBRE_EXTRA_DISK_GB = 5 @staticmethod - def _qemu_wait_ssh(ip, user="erplibre", timeout=180): - """Attend que le SSH réponde (le sshd peut mettre du temps à démarrer - après le boot, surtout sur Fedora). True si joignable.""" + def _qemu_wait_ssh(ip, user="erplibre", timeout=720): + """Attend que sshd réponde ET que cloud-init soit TERMINÉ, via des + 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 = ( "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null " "-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 + ssh_up = False while time.time() < deadline: try: res = subprocess.run( - f"ssh {opts} {user}@{ip} true", + f"ssh {opts} {user}@{ip} {shlex.quote(probe)}", shell=True, capture_output=True, - timeout=15, + timeout=20, + text=True, ) + out = res.stdout or "" except (OSError, subprocess.SubprocessError): - res = None - if res is not None and res.returncode == 0: + out = "" + if out.strip(): + ssh_up = True # sshd a répondu + if any(k in out for k in ready): return True 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): """Script d'installation ERPLibre exécuté DANS la VM (curl/git/make