#!/usr/bin/env bash # © 2026 TechnoLibre (http://www.technolibre.ca) # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl) # Installe et éprouve le projet sur chaque système du catalogue de machines # virtuelles de TODO (ERPLibre), chacun dans un conteneur podman : sous un # compte ordinaire qui passe par sudo, ./install_dev.sh, puis make test, # make test_long, make test_navigateur, l'essai de démarrage d'Electron, et # le serveur de Vite. Les scripts PowerShell s'éprouvent sous pwsh. # # scripts/verifier_systemes.sh tout le catalogue # scripts/verifier_systemes.sh debian-13 arch ces systèmes # scripts/verifier_systemes.sh --liste les systèmes connus # # Ce qu'un conteneur n'éprouve pas : le bac à sable d'Electron (pas d'espaces # de noms d'utilisateur, Electron s'y lance sans), une autre architecture # que celle de l'hôte, NixOS lui-même (l'image nix éprouve shell.nix, sur # une base qui n'est pas NixOS), une vraie session graphique. # # GTT_PARALLELES (4 par défaut) borne les conteneurs simultanés ; les # journaux vont dans GTT_JOURNAUX, un dossier temporaire par défaut. Les # téléchargements — npm, Electron, Chromium, Node — se partagent entre # conteneurs par des volumes nommés gttl-cache-*. set -euo pipefail racine="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" # Système, image : le catalogue de script/qemu/deploy_qemu.py d'ERPLibre. # Proxmox VE 9 est Debian 13 sous ses paquets : debian-13 le représente. CATALOGUE=' ubuntu-24.04 docker.io/library/ubuntu:24.04 ubuntu-25.10 docker.io/library/ubuntu:25.10 ubuntu-26.04 docker.io/library/ubuntu:26.04 debian-12 docker.io/library/debian:12 debian-13 docker.io/library/debian:13 fedora-41 docker.io/library/fedora:41 fedora-42 docker.io/library/fedora:42 fedora-43 docker.io/library/fedora:43 fedora-44 docker.io/library/fedora:44 almalinux-9 docker.io/library/almalinux:9 almalinux-10 docker.io/library/almalinux:10 rocky-9 docker.io/rockylinux/rockylinux:9 rocky-10 docker.io/rockylinux/rockylinux:10 opensuse-leap-16.0 registry.opensuse.org/opensuse/leap:16.0 opensuse-tumbleweed registry.opensuse.org/opensuse/tumbleweed:latest arch docker.io/library/archlinux:latest nix docker.io/nixos/nix:latest pwsh mcr.microsoft.com/powershell:latest ' usage() { sed -n '9,12p' "$0" | sed 's/^# \{0,1\}//' } image_de() { printf '%s\n' "$CATALOGUE" | awk -v s="$1" '$1 == s { print $2 }' } systemes=() for argument in "$@"; do case "$argument" in --liste) printf '%s\n' "$CATALOGUE" | awk 'NF { print $1 }' exit 0 ;; -h | --help) usage exit 0 ;; -*) usage >&2 exit 2 ;; *) [ -n "$(image_de "$argument")" ] || { echo "système inconnu : $argument (--liste)" >&2; exit 2; } systemes+=("$argument") ;; esac done if [ ${#systemes[@]} -eq 0 ]; then while read -r s _; do [ -n "$s" ] && systemes+=("$s") done <<< "$CATALOGUE" fi command -v podman > /dev/null || { echo "podman introuvable." >&2; exit 1; } journaux="${GTT_JOURNAUX:-$(mktemp -d "${TMPDIR:-/tmp}/verifier_systemes.XXXXXX")}" mkdir -p "$journaux" # Étiquette des conteneurs de cette vérification : une interruption arrête # ceux-là, et aucun autre conteneur de la machine. ETIQUETTE="gttl-verification=$$" arreter() { podman ps -q --filter "label=$ETIQUETTE" | xargs -r podman stop -t 2 > /dev/null 2>&1 || true } trap 'arreter; exit 130' INT trap 'arreter; exit 143' TERM # Copie de l'arbre de travail tel qu'il est — fichiers suivis et nouveaux # non ignorés —, sans node_modules : chaque conteneur part de zéro. source_copie="$journaux/source" rm -rf "$source_copie" mkdir -p "$source_copie" (cd "$racine" && git ls-files -co --exclude-standard -z | tar --null -cf - -T - | tar -xf - -C "$source_copie") # Préparation sous root, par famille : sudo et un compte ordinaire « dev » # qui l'emploie sans mot de passe, comme un poste de développement. Son uid # est le même dans chaque conteneur — 1500, libre aussi sous Ubuntu, dont # l'image donne 1000 à son propre compte — : les caches partagés ne # changent jamais de propriétaire. preparation() { case "$1" in ubuntu-* | debian-*) echo 'apt-get update -q && DEBIAN_FRONTEND=noninteractive apt-get install -y -q sudo' ;; fedora-* | almalinux-* | rocky-*) echo 'dnf install -y sudo shadow-utils' ;; opensuse-*) echo 'zypper --non-interactive install sudo shadow' ;; arch) echo 'pacman -Syu --noconfirm sudo' ;; esac } # Étapes sous le compte dev, chacune annoncée par une ligne « === étape # statut » que le bilan relit. L'essai et le serveur de Vite ne se jouent # que sur une installation réussie. ETAPES_DEV=' etape() { local nom="$1"; shift; if "$@"; then echo "=== $nom ok"; else echo "=== $nom ÉCHEC"; return 1; fi; } cd "$HOME/projet" etape install_dev ./install_dev.sh || exit 1 etape test make test etape test_long make test_long etape test_navigateur make test_navigateur etape essai_demarrage env GTT_SANS_BAC_A_SABLE=1 make essai_demarrage web() { ./run.sh --web > "$HOME/web.log" 2>&1 & scripts/installation/avec_node.sh node -e " const fin = Date.now() + 60000; (async () => { while (Date.now() < fin) { try { const page = await (await fetch(\"http://localhost:5173/\")).text(); if (page.includes(\"/src/main.js\")) process.exit(0); } catch {} await new Promise((r) => setTimeout(r, 500)); } process.exit(1); })();" } etape web web ' verifier_linux() { local systeme="$1" image="$2" podman run --rm --env-host=false --label "$ETIQUETTE" \ -v "$source_copie:/source:ro" \ -v gttl-cache-npm:/home/dev/.npm \ -v gttl-cache-outils:/home/dev/.cache \ "$image" bash -c " set -e $(preparation "$systeme") useradd -m -u 1500 -s /bin/bash dev 2> /dev/null || true echo 'dev ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/dev cp -a /source /home/dev/projet chown -R dev: /home/dev/projet chown dev: /home/dev /home/dev/.npm /home/dev/.cache sudo -u dev -H bash -c '$ETAPES_DEV' " } # L'image nix tourne sous root, sans sudo ni useradd : elle éprouve # shell.nix, que GTT_FAMILLE désigne, sur une base qui n'est pas NixOS. # ./install.sh y entre de lui-même ; make n'existe que dans shell.nix, où # les étapes suivantes s'exécutent, comme sous NixOS. verifier_nix() { podman run --rm --env-host=false --label "$ETIQUETTE" -e GTT_FAMILLE=nixos \ -v "$source_copie:/source:ro" \ "$2" sh -c " cp -a /source /root/projet export HOME=/root cd /root/projet if nix-shell -p bash --run ./install.sh; then echo '=== entree_shell_nix ok'; else echo '=== entree_shell_nix ÉCHEC'; exit 1; fi nix-shell scripts/installation/shell.nix --run 'bash -c '\''$ETAPES_DEV'\''' " } # Les scripts PowerShell sous pwsh : l'analyse syntaxique de chacun, puis la # lecture de .node-version, le choix de l'archive dans SHASUMS256.txt, et le # téléchargement, la vérification et l'extraction de Node pour win-x64 — # node.exe ne s'exécute pas ici, son extraction se constate. verifier_pwsh() { podman run --rm --env-host=false --label "$ETIQUETTE" -v "$source_copie:/source:ro" "$2" pwsh -NoProfile -Command ' $ErrorActionPreference = "Stop" $erreurs = 0 foreach ($f in Get-ChildItem /source/scripts/windows/*.ps1) { $jetons = $null; $fautes = $null [void][System.Management.Automation.Language.Parser]::ParseFile($f.FullName, [ref]$jetons, [ref]$fautes) if ($fautes.Count -gt 0) { $erreurs++; Write-Host "$($f.Name) : $($fautes[0].Message)" } } if ($erreurs -eq 0) { Write-Host "=== analyse ok" } else { Write-Host "=== analyse ÉCHEC"; exit 1 } Copy-Item -Recurse /source /tmp/projet $env:LOCALAPPDATA = "/tmp/donnees" $env:PROCESSOR_ARCHITECTURE = "AMD64" try { . /tmp/projet/scripts/windows/commun.ps1 } catch { Write-Host "=== chargement ÉCHEC $_"; exit 1 } Write-Host "=== chargement ok" $choix = Find-GttArchiveNode @("a1 node-v26.10.0-win-x64.7z", "b2 node-v26.10.0-win-x64.zip", "c3 node-v26.10.0-win-arm64.zip") "x64" if ("$choix" -eq "b2 node-v26.10.0-win-x64.zip" -and (Get-GttMajeure) -match "^[0-9]+$") { Write-Host "=== choix ok" } else { Write-Host "=== choix ÉCHEC $choix"; exit 1 } try { Install-GttNode } catch { } if (Test-Path (Join-Path (Get-GttDossierNode) "node.exe")) { Write-Host "=== node_windows ok" } else { Write-Host "=== node_windows ÉCHEC"; exit 1 } ' } verifier() { local systeme="$1" image journal image="$(image_de "$systeme")" journal="$journaux/$systeme.log" { echo "### $systeme ($image)" case "$systeme" in nix) verifier_nix "$systeme" "$image" ;; pwsh) verifier_pwsh "$systeme" "$image" ;; *) verifier_linux "$systeme" "$image" ;; esac } > "$journal" 2>&1 || true echo "$systeme : $(grep -c '^=== .* ok$' "$journal") ok, $(grep -c '^=== .* ÉCHEC' "$journal") échec(s)" } paralleles="${GTT_PARALLELES:-4}" echo "Journaux : $journaux" for systeme in "${systemes[@]}"; do while [ "$(jobs -rp | wc -l)" -ge "$paralleles" ]; do sleep 2 done verifier "$systeme" & done wait echo echo "Bilan :" statut=0 for systeme in "${systemes[@]}"; do ligne="$(grep '^=== ' "$journaux/$systeme.log" | sed 's/^=== //' | tr '\n' ' ')" case "$ligne" in '' | *ÉCHEC*) statut=1 ;; esac printf ' %-22s %s\n' "$systeme" "${ligne:-aucune étape (voir $journaux/$systeme.log)}" done exit "$statut"