[ADD] install: openSUSE support, mirrors and packages

Tumbleweed is the only catalog entry whose qpdf already clears the pikepdf
threshold: 12.3.2 against 12.2 required, so the half-hour qpdf build under
s390x emulation never runs there. It is also the only family foreign to
both RHEL and Debian, and it brings zypper, absent from the repository:
wired into the install_dev.sh dispatch, a dependency script, the remote
bootstrap and the desktop block.

Four SUSE traps, all met on real machines. Global options precede the
subcommand, and misplaced ones abort the call; without
"--auto-agree-with-licenses" zypper waits for an answer nobody gives in a
detached install. A compat package that PROVIDES another under a different
name makes zypper raise a conflict and drop the whole batch, so only what
nothing already provides is requested. pkg-config no longer exists as an
RPM, only as a capability. And a single mirror, unreachable one day, sent
everyone back to Europe -- three are probed in order now.

CentOS Stream is dropped again: it served as a canary but 10 duplicates
what Alma and Rocky already cover.

--- FR ---

Tumbleweed est la seule entrée du catalogue dont qpdf franchit déjà le
seuil de pikepdf : 12.3.2 contre 12.2 exigé, si bien que la demi-heure de
compilation de qpdf sous émulation s390x ne s'y déclenche jamais. C'est
aussi la seule famille étrangère à RHEL comme à Debian, et elle apporte
zypper, absent du dépôt : câblé dans l'aiguillage d'install_dev.sh, un
script de dépendances, l'amorçage distant et le bloc bureau.

Quatre pièges SUSE, tous rencontrés sur machine réelle. Les options
globales précèdent la sous-commande, mal placées elles arrêtent l'appel ;
sans « --auto-agree-with-licenses » zypper attend une réponse que personne
ne donne dans une installation détachée. Un paquet compat qui FOURNIT un
autre sous un nom différent fait lever un conflit à zypper, qui abandonne
le lot entier : on ne demande donc que ce que rien ne fournit déjà.
pkg-config n'existe plus comme RPM, seulement comme capacité. Et un miroir
unique, injoignable un jour, renvoyait tout le monde en Europe — trois
sont désormais sondés dans l'ordre.

CentOS Stream repart : il servait de canari, mais 10 double ce qu'Alma et
Rocky couvrent déjà.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-08-11 20:38:12 -04:00
parent 96464ab0eb
commit 7120732c25
12 changed files with 522 additions and 17 deletions

View file

@ -7,7 +7,8 @@
- **DNS** : `script/deployment/update_dns_cloudflare.py`
Plateformes supportées : Ubuntu 24.04 / 25.10 / 26.04, Linux Mint 22.3,
Debian 12, AlmaLinux 9+, Rocky Linux 9+, Arch Linux, macOS (pyenv),
Debian 12, AlmaLinux 9+, Rocky Linux 9+, openSUSE Tumbleweed, Arch Linux,
macOS (pyenv),
Windows (WSL/Docker).
Ubuntu 20.04 et 22.04 sont abandonnées : pikepdf exige qpdf >= 12.2, compilé

View file

@ -58,7 +58,8 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi
- Support Selenium grid from selenium_lib.py
- Add addons OnlyOffice, Cetmix, OCA automation, OCA shopfloor
- Deploy ERPLibre VMs with QEMU/KVM from cloud images (Ubuntu, Debian, Fedora,
AlmaLinux, Rocky Linux, Arch) on amd64, arm64 and s390x, with a menu to list,
AlmaLinux, Rocky Linux, openSUSE Tumbleweed, Arch) on amd64,
arm64 and s390x, with a menu to list,
test, resize, delete and clean up
- Graphical VMs: a server / desktop choice offering GNOME or Cinnamon (the
Linux Mint desktop), with remote access through xrdp — TigerVNC on Arch
@ -111,7 +112,8 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi
- Support de la grille Selenium depuis selenium_lib.py
- Ajout des addons OnlyOffice, Cetmix, OCA automation, OCA shopfloor
- Déploiement de VM ERPLibre en QEMU/KVM depuis des images cloud (Ubuntu,
Debian, Fedora, AlmaLinux, Rocky Linux, Arch) en amd64, arm64 et s390x, avec
Debian, Fedora, AlmaLinux, Rocky Linux, openSUSE Tumbleweed,
Arch) en amd64, arm64 et s390x, avec
un menu pour lister, tester, redimensionner, supprimer et nettoyer
- VM graphiques : un choix serveur / bureau proposant GNOME ou Cinnamon (le
bureau de Linux Mint), avec accès distant par xrdp — TigerVNC sur Arch

View file

@ -32,7 +32,8 @@ Recréer l'environnement virtuel, utiliser le guide d'installation depuis l'outi
- Support de la grille Selenium depuis selenium_lib.py
- Ajout des addons OnlyOffice, Cetmix, OCA automation, OCA shopfloor
- Déploiement de VM ERPLibre en QEMU/KVM depuis des images cloud (Ubuntu,
Debian, Fedora, AlmaLinux, Rocky Linux, Arch) en amd64, arm64 et s390x, avec
Debian, Fedora, AlmaLinux, Rocky Linux, openSUSE Tumbleweed,
Arch) en amd64, arm64 et s390x, avec
un menu pour lister, tester, redimensionner, supprimer et nettoyer
- VM graphiques : un choix serveur / bureau proposant GNOME ou Cinnamon (le
bureau de Linux Mint), avec accès distant par xrdp — TigerVNC sur Arch

View file

@ -32,7 +32,8 @@ Recreating the virtual environment, use installation guide from tool `make`.
- Support Selenium grid from selenium_lib.py
- Add addons OnlyOffice, Cetmix, OCA automation, OCA shopfloor
- Deploy ERPLibre VMs with QEMU/KVM from cloud images (Ubuntu, Debian, Fedora,
AlmaLinux, Rocky Linux, Arch) on amd64, arm64 and s390x, with a menu to list,
AlmaLinux, Rocky Linux, openSUSE Tumbleweed, Arch) on amd64,
arm64 and s390x, with a menu to list,
test, resize, delete and clean up
- Graphical VMs: a server / desktop choice offering GNOME or Cinnamon (the
Linux Mint desktop), with remote access through xrdp — TigerVNC on Arch

View file

@ -1,6 +1,16 @@
#!/usr/bin/env bash
if [[ "${OSTYPE}" == "linux-gnu" ]]; then
# « $OSTYPE » ne decrit PAS le systeme : bash le fige au triplet avec lequel il
# a ete compile. openSUSE batit bash en « x86_64-suse-linux » et le renseigne
# donc a « linux », la ou Debian et Fedora donnent « linux-gnu ». Le test
# echouait, sans else, donc sans une ligne de sortie ni code d'erreur : AUCUNE
# dependance systeme n'etait posee, install_system() rendait 0, et
# l'installation s'arretait une heure plus tard sur « [Errno 2] ... 'cc' ».
# « uname -s » interroge le noyau et ne depend pas de la facon dont bash a ete
# bati. install_dev_extra_ubuntu.sh garde OSTYPE : il ne vise qu'Ubuntu.
OS_KERNEL="$(uname -s)"
if [[ "${OS_KERNEL}" == "Linux" ]]; then
source /etc/os-release
if [[ "${ID}" == "ubuntu" ]]; then
# 20.04 et 22.04 retirees : leur chaine d'outils ne suffit plus a ERPLibre
@ -24,6 +34,10 @@ if [[ "${OSTYPE}" == "linux-gnu" ]]; then
./script/install/install_debian_dependency.sh
elif [[ "${ID}" == "arch" ]]; then
./script/install/install_arch_linux.sh
elif [[ "${ID}" == opensuse* || "${ID_LIKE}" == *"suse"* ]]; then
# openSUSE Tumbleweed / Leap, SLES : gestionnaire zypper.
echo "\n---- SUSE installation process started ----"
./script/install/install_suse_dependency.sh
elif [[ "${ID}" == "fedora" || "${ID_LIKE}" == *"fedora"* || "${ID_LIKE}" == *"rhel"* ]]; then
# AlmaLinux et Rocky declarent ID_LIKE="rhel centos fedora" : elles passent
# donc par ce meme script, qui aiguille dnf.
@ -31,9 +45,15 @@ if [[ "${OSTYPE}" == "linux-gnu" ]]; then
./script/install/install_fedora_dependency.sh
else
./script/install/install_debian_dependency.sh
echo "Your Linux system is not supported, only support Ubuntu 24.04, 25.10, 26.04, Linux Mint 22.3, Debian, Fedora, AlmaLinux, Rocky Linux, Arch."
echo "Your Linux system is not supported, only support Ubuntu 24.04, 25.10, 26.04, Linux Mint 22.3, Debian, Fedora, AlmaLinux, Rocky Linux, openSUSE, Arch."
fi
elif [[ "${OSTYPE}" == "darwin"* ]]; then
elif [[ "${OS_KERNEL}" == "Darwin" ]]; then
echo "\n---- Darwin installation process started ----"
./script/install/install_OSX_dependency.sh
else
# Un aiguillage muet qui rend 0 est le pire des cas : l'installation continue
# sur un systeme depourvu de tout. Meme sans savoir quoi poser, on le dit et
# on arrete.
echo "Systeme non reconnu (uname -s = ${OS_KERNEL}) : aucune dependance installee."
exit 1
fi

View file

@ -0,0 +1,337 @@
#!/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 la famille SUSE (zypper). Équivalent SUSE
# de install_debian_dependency.sh et install_fedora_dependency.sh.
#
# Visé : openSUSE Tumbleweed, la seule distribution du catalogue dont qpdf
# (12.3.2) dépasse déjà le seuil de pikepdf 10 — la compilation de qpdf, une
# demi-heure sous émulation s390x, ne s'y déclenche pas.
. ./env_var.sh
EL_USER=${USER}
# « --non-interactive » est une option GLOBALE de zypper, elle précède la
# sous-commande. Les deux autres appartiennent à « install » et doivent la
# SUIVRE : placées avant, zypper répond « The flag
# --auto-agree-with-licenses is not known » et s'arrête.
#
# On demande à zypper quelles options il accepte, plutôt que de le supposer :
# la famille SUSE n'est pas testée ici, et un drapeau inconnu fait échouer la
# commande entière au lieu du seul paquet visé.
# --auto-agree-with-licenses : sinon zypper attend l'acceptation d'une
# licence, réponse que personne ne donnera dans une installation détachée.
# --ignore-unknown : saute un nom de paquet absent au lieu de refuser tout
# le lot, l'équivalent de « --skip-unavailable » côté dnf.
ZYP="sudo zypper --non-interactive"
ZYP_HELP="$(zypper install --help 2>&1 || true)"
ZYP_LIC=""
ZYP_IGN=""
case "${ZYP_HELP}" in
*--auto-agree-with-licenses*) ZYP_LIC="--auto-agree-with-licenses" ;;
esac
case "${ZYP_HELP}" in
*--ignore-unknown*) ZYP_IGN="--ignore-unknown" ;;
esac
echo "zypper : options retenues = ${ZYP_LIC} ${ZYP_IGN}"
# Lot obligatoire : un paquet manquant doit se voir.
ZYP_IN="${ZYP} install ${ZYP_LIC}"
# Lot best-effort : un nom absent est sauté.
ZYP_SOFT="${ZYP_IN} ${ZYP_IGN}"
# openSUSE remplace des paquets par des variantes « compat » qui les FOURNISSENT
# sans en porter le nom : sur Tumbleweed, zlib-ng-compat-devel fournit
# zlib-devel et se trouve posé d'office. Demander « zlib-devel » par son nom
# force alors un échange, zypper soulève un conflit — et --non-interactive n'a
# aucune solution par défaut : il énumère les choix, puis ABANDONNE le lot
# entier. C'est ce qui a arrêté l'installation sur amd64, en emportant les huit
# autres dépendances de pyenv qui, elles, ne posaient aucun problème.
# « --ignore-unknown » ne couvre que les noms absents, jamais les conflits.
#
# On ne demande donc que ce que rien ne fournit déjà. rpm interroge les
# « Provides », pas seulement les noms : c'est la seule autorité sur la
# question. Effet de bord bienvenu, une relance ne redemande plus rien.
zyp_filter() {
local p
for p in "$@"; do
rpm -q --whatprovides "${p}" > /dev/null 2>&1 || printf '%s\n' "${p}"
done
}
# Un telechargement tronque par le miroir fait echouer le LOT ENTIER, et
# zypper ne reessaie pas. Vecu : « libLLVM22 ... OpenSSL SSL_read: unexpected
# eof while reading » a emporte « rust cargo », et l'absence de cargo n'est
# apparue qu'une heure plus tard, dans la compilation de bcrypt. Une seconde
# tentative suffit dans la quasi-totalite des cas.
zyp_retry() {
local cmd=("$@")
"${cmd[@]}" && return 0
echo " zypper a echoue (reseau ?), seconde tentative..." >&2
"${cmd[@]}"
}
zyp_in() {
local todo
mapfile -t todo < <(zyp_filter "$@")
if [ "${#todo[@]}" -eq 0 ]; then
echo " deja fourni : $*"
return 0
fi
zyp_retry ${ZYP_IN} "${todo[@]}"
}
zyp_soft() {
local todo
mapfile -t todo < <(zyp_filter "$@")
if [ "${#todo[@]}" -eq 0 ]; then
echo " deja fourni : $*"
return 0
fi
zyp_retry ${ZYP_SOFT} "${todo[@]}"
}
#--------------------------------------------------
# Miroir des dépôts
#--------------------------------------------------
# Le redirecteur officiel d'openSUSE n'est PAS géographique : mesuré depuis
# Montréal sur les métadonnées oss s390x (15 Mo), download.opensuse.org met
# 23,8 s — il sert depuis l'Europe — contre 2,7 s pour mirrors.rit.edu.
#
# Chaque miroir est SONDÉ sur le chemin de l'architecture courante : tous ne
# répliquent pas les architectures secondaires, et rit.edu sert justement
# zsystems mais pas x86_64. Aucun sondage concluant : on garde les dépôts de
# l'image, donc le comportement d'avant.
# Plusieurs miroirs, sondés dans l'ordre sur le chemin exact du produit et
# de l'architecture : aucun ne réplique tout. Relevé le 2026-08-12 —
# csclub sert Leap mais pas Tumbleweed, leaseweb sert Tumbleweed x86_64
# mais pas les ports zsystems, et rit.edu — seule entrée jusqu'ici — était
# injoignable ce jour-là, ce qui renvoyait tout le monde en Europe.
ZYP_MIRRORS="https://mirror.csclub.uwaterloo.ca/opensuse https://mirrors.rit.edu/opensuse https://mirror.us.leaseweb.net/opensuse"
# Leap et Tumbleweed sont deux PRODUITS, pas deux versions du même : arbres de
# dépôts différents, et surtout une mise à jour de nature différente (plus bas).
SUSE_ID="$([ -r /etc/os-release ] && . /etc/os-release && echo "${ID}")"
SUSE_VER="$([ -r /etc/os-release ] && . /etc/os-release && echo "${VERSION_ID}")"
case "${SUSE_ID}" in
*tumbleweed*) SUSE_ROLLING=1 ;;
*) SUSE_ROLLING=0 ;;
esac
if [ "${SUSE_ROLLING}" = 1 ]; then
# Tumbleweed isole les architectures secondaires sous /ports/.
zp=tumbleweed
[ "$(uname -m)" = s390x ] && zp=ports/zsystems/tumbleweed
else
# Leap 16 unifie tout : un seul arbre, avec s390x dedans. Les chemins
# /ports/ équivalents rendent 404 — relevé, pas déduit.
zp="distribution/leap/${SUSE_VER}"
fi
for zm in ${ZYP_MIRRORS}; do
if curl -fsS --max-time 20 -o /dev/null \
"${zm}/${zp}/repo/oss/repodata/repomd.xml"; then
sudo sed -i "s|https\?://download\.opensuse\.org|${zm}|g" \
/etc/zypp/repos.d/*.repo 2> /dev/null || true
echo "miroir openSUSE : ${zm}"
break
fi
done
echo -e "\n---- Rafraichissement des depots ----"
# Tumbleweed est ROLLING : installer sans rafraîchir mène à des paquets
# introuvables (l'index local pointe des versions déjà retirées du miroir).
sudo zypper --non-interactive refresh || true
# Mise à jour COMPLÈTE, obligatoire pour la même raison qu'Arch : une rolling
# release ne supporte PAS les mises à jour partielles. L'image cloud est un
# instantané figé dont les dépôts ont avancé — vécu sur s390x, git 2.54
# réclamait un perl-Git bâti contre un perl-base plus ancien que celui de
# l'image. zypper proposait alors trois solutions et ATTENDAIT un choix ;
# « --non-interactive » prend le défaut, « c » = annuler, et tout s'arrêtait.
echo -e "\n---- Mise a jour complete (dup) ----"
${ZYP} dup ${ZYP_LIC} --allow-vendor-change || true
#--------------------------------------------------
# Outils de compilation (build Python via pyenv, extensions Python)
#--------------------------------------------------
echo -e "\n---- Outils de developpement ----"
# Les compilateurs sont installés EXPLICITEMENT, jamais par le seul motif :
# un « pattern » absent ou incomplet laisserait le système sans g++, et
# l'échec n'apparaîtrait qu'à la compilation de numpy — la leçon d'EL, où le
# groupe « development-tools » n'existe pas et où le repli ne partait jamais.
# Les noms non versionnés existent bien dans Tumbleweed (relevé dans le
# primary.xml du dépôt oss) : « gcc » y est un méta-paquet qui tire gcc15.
zyp_in gcc gcc-c++ make automake patch
retVal=$?
if [[ ${retVal} -ne 0 ]]; then
echo "zypper install compilers error."
exit 1
fi
# Le motif ensuite, en complément et sans jamais bloquer.
${ZYP_IN} -t pattern devel_basis > /dev/null 2>&1 || true
# Python appelle son compilateur par le nom gravé dans sysconfig, souvent
# « cc ». Sur openSUSE ce lien n'est garanti par aucune déclaration de
# paquet — vécu sur amd64 : « cc -pthread … error: [Errno 2] No such file or
# directory » à la compilation de python-ldap, alors que gcc était installé.
if ! command -v cc > /dev/null 2>&1 && command -v gcc > /dev/null 2>&1; then
sudo ln -sf "$(command -v gcc)" /usr/local/bin/cc
echo "cc -> $(command -v gcc)"
fi
#--------------------------------------------------
# PostgreSQL
#--------------------------------------------------
echo -e "\n---- Install PostgreSQL Server ----"
zyp_in postgresql-server postgresql-contrib postgresql-server-devel
retVal=$?
if [[ ${retVal} -ne 0 ]]; then
echo "zypper install postgresql installation error."
exit 1
fi
# openSUSE n'initialise pas le cluster : le service le fait au 1er démarrage,
# mais seulement si le répertoire de données est vide.
sudo systemctl enable --now postgresql 2> /dev/null || true
# PostGIS : absent des dépôts Tumbleweed s390x. Optionnel, ne bloque pas.
zyp_soft postgis || echo "PostGIS non installe (optionnel)."
echo -e "\n---- Creating the ERPLibre PostgreSQL User ----"
sudo su - postgres -c "createuser -s ${EL_USER}" 2> /dev/null || true
#--------------------------------------------------
# Dépendances de build (extensions Python, Odoo)
#--------------------------------------------------
echo -e "\n--- Installing suse dependency --"
# git-daemon : comme sur Fedora, « git daemon » n'est pas dans le paquet git de
# base. ERPLibre sert son manifeste par git://127.0.0.1:9418/ pendant
# « repo sync » — sans lui, « Connection refused » et synchro impossible.
# Rust n'est nécessaire QUE là où les roues manquent : bcrypt et cryptography
# portent une extension Rust, et sur amd64 leurs roues masquent le besoin. Sur
# s390x tout compile, et bcrypt s'arrête sur « error: can't find Rust
# compiler ». Tumbleweed livre 1.94, bien au-dessus du 1.78 qu'exige le
# Cargo.lock v4 de cryptography. On évite ainsi ~200 Mo inutiles sur amd64.
if [ "$(uname -m)" = "s390x" ]; then
zyp_soft rust cargo
if ! command -v cargo > /dev/null 2>&1; then
# FATAL, et non plus un simple avertissement : sur s390x, bcrypt et
# cryptography n'ont aucune roue et portent une extension Rust. Sans
# cargo l'installation est perdue d'avance — mais elle ne le montrait
# qu'une heure plus tard, au fond d'une trace pip. Mieux vaut s'arreter
# ici, ou la cause est encore lisible.
echo "cargo introuvable apres deux tentatives." >&2
echo " bcrypt et cryptography ne peuvent pas se compiler sans lui." >&2
echo " Verifiez le miroir zypper, puis relancez." >&2
exit 1
fi
# Sans roue s390x, TOUT se compile contre les en-têtes de la distribution.
# Cette liste reprend donc celles d'apt et de dnf, aux noms d'openSUSE près
# — relevés dans l'index oss de zsystems, jamais devinés. La tenir alignée
# ici plutôt que d'attendre : trois fois de suite, le même manque a été
# découvert d'abord par Debian, puis retrouvé une distribution à la fois.
# tbb-devel manifold3d refuse de configurer sans lui, et le dit dans un
# message de CMake, pas de compilateur : « Parallel mode
# enabled, but tbb was not found ».
# clang-devel pymupdf charge libclang.so par ctypes (lien juste après).
# cmake, pkgconf-pkg-config : nécessaires à el_qpdf_ensure, et ils
# n'arrivent que plus bas dans le script.
#
# « pkgconf-pkg-config » et non « pkg-config » : aucun RPM ne porte ce
# dernier nom dans Tumbleweed, il n'existe plus que comme capacité fournie
# par le premier. La capacité résout aujourd'hui, le nom réel ne dépend
# de rien.
#
# GeographicLib est absent : openSUSE n'empaquette que le binding Python,
# pas la bibliothèque C++. Rien à poser, contrairement à apt et dnf.
zyp_soft qpdf-devel libjpeg8-devel cmake pkgconf-pkg-config \
tbb-devel geos-devel proj-devel krb5-devel ninja clang-devel llvm-devel
# Tumbleweed livre qpdf 12.3.2 : l'appel ne fait que le constater. Il est là
# pour que les trois familles suivent la même règle.
el_qpdf_ensure
# pymupdf charge « libclang.so » par son nom nu, via ctypes ; le paquet ne
# livre qu'un nom versionné. Il ne manque que le lien — même correctif que
# côté apt et dnf.
for d in /usr/lib64 /usr/lib; do
if [ -d "${d}" ] && [ ! -e "${d}/libclang.so" ]; then
so="$(ls -1 "${d}"/libclang.so.* 2> /dev/null | sort -V | tail -1)"
if [ -n "${so}" ]; then
sudo ln -s "${so}" "${d}/libclang.so" && sudo ldconfig
echo "libclang.so -> ${so}"
fi
fi
done
fi
zyp_soft \
git git-daemon wget libxslt-devel libzip-devel openldap2-devel \
cyrus-sasl-devel libffi-devel libbz2-devel gnu_parallel swig cmake \
portaudio-devel cups-devel xmlsec1 xmlsec1-openssl-devel \
libmariadb-devel freetds-devel
retVal=$?
if [[ ${retVal} -ne 0 ]]; then
echo "zypper suse tool installation error."
exit 1
fi
# Dépendances de build pour pyenv (compilation de CPython) — CRITIQUE.
echo -e "\n---- Dependances pyenv (compilation Python) ----"
zyp_in \
make gcc zlib-devel libbz2-devel readline-devel sqlite3-devel \
libopenssl-devel tk-devel libffi-devel xz-devel patch findutils
retVal=$?
if [[ ${retVal} -ne 0 ]]; then
echo "zypper pyenv dependencies installation error."
exit 1
fi
# Dépendances selenium / bindings.
zyp_soft \
cairo-devel python3-devel pkgconf-pkg-config gobject-introspection-devel \
libXt-devel || echo "Dependances selenium partielles (optionnel)."
#--------------------------------------------------
# Node.js + npm (rtlcss, less)
#--------------------------------------------------
echo -e "\n---- Installing nodeJS NPM and rtlcss ----"
# openSUSE versionne les paquets node (nodejs22, nodejs24…) et ne fournit pas
# toujours de méta-paquet « nodejs ». On prend le plus récent disponible.
NODE_OK=0
for pkg in nodejs24 nodejs22 nodejs20 nodejs; do
if ${ZYP_IN} "${pkg}" "${pkg/nodejs/npm}" 2> /dev/null; then
NODE_OK=1
echo "node fourni par ${pkg}"
break
fi
done
if [[ ${NODE_OK} -ne 1 ]] || ! command -v npm > /dev/null 2>&1; then
echo "zypper 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 || echo "npm install (prettier/plugin-xml): erreur (optionnel)."
sudo ln -fs /usr/local/bin/lessc /usr/bin/lessc 2> /dev/null || true
#--------------------------------------------------
# nginx (optionnel)
#--------------------------------------------------
if [ "${EL_INSTALL_NGINX}" = "True" ]; then
echo -e "\n---- Installing nginx ----"
zyp_soft nginx || echo "nginx: erreur (optionnel)."
fi
#--------------------------------------------------
# wkhtmltopdf (optionnel)
#--------------------------------------------------
if [ "${EL_INSTALL_WKHTMLTOPDF}" = "True" ]; then
if ! command -v wkhtmltopdf > /dev/null 2>&1; then
echo -e "\n---- Installing wkhtml (best-effort) ----"
# wkhtmltopdf ne publie AUCUN paquet openSUSE. Le dépôt de la distribution
# fournit « wkhtmltopdf » quand il existe pour l'architecture ; sinon on
# renonce, Odoo sait imprimer sans lui (rendu dégradé des PDF).
zyp_soft wkhtmltopdf || echo "wkhtmltopdf non installe (optionnel)."
else
echo -e "\n---- Already installed wkhtml ----"
fi
fi
echo -e "\n---- SUSE dependency installation done ----"

View file

@ -182,11 +182,16 @@ Catalog, per architecture (`deploy_qemu.py` is the source of truth):
| fedora | `41`, `42` (default), `43`, `44` | ✔ | ✔ | `43` only |
| almalinux | `9` (default), `10` | ✔ | ✔ | ✔ |
| rocky | `9`, `10` (default) | ✔ | ✔ | ✔ |
| opensuse | `tumbleweed` | ✔ | ✔ | ✔ |
| arch | `latest` | ✔ | — | — |
Fedora builds s390x only for the current release, and on a separate tree
(`fedora-secondary`) — hence the single version there.
`opensuse` is openSUSE Tumbleweed, the only entry whose qpdf (12.3.2) already
clears the pikepdf threshold: the half-hour qpdf build never runs there, which
matters under s390x emulation.
Provide an explicit image path as a positional argument to override the
automatic download location.
@ -207,11 +212,16 @@ Catalogue, par architecture (`deploy_qemu.py` fait autorité) :
| fedora | `41`, `42` (défaut), `43`, `44` | ✔ | ✔ | `43` seule |
| almalinux | `9` (défaut), `10` | ✔ | ✔ | ✔ |
| rocky | `9`, `10` (défaut) | ✔ | ✔ | ✔ |
| opensuse | `tumbleweed` | ✔ | ✔ | ✔ |
| arch | `latest` | ✔ | — | — |
Fedora ne construit s390x que pour la version courante, et sur une
arborescence à part (`fedora-secondary`) — d'où la version unique.
`opensuse` désigne openSUSE Tumbleweed, seule entrée dont qpdf (12.3.2)
dépasse déjà le seuil de pikepdf : la compilation de qpdf, une demi-heure, ne
s'y déclenche jamais — ce qui compte sous émulation s390x.
Fournissez un chemin d'image en argument positionnel pour surcharger
l'emplacement de téléchargement automatique.

View file

@ -101,11 +101,16 @@ Catalogue, par architecture (`deploy_qemu.py` fait autorité) :
| fedora | `41`, `42` (défaut), `43`, `44` | ✔ | ✔ | `43` seule |
| almalinux | `9` (défaut), `10` | ✔ | ✔ | ✔ |
| rocky | `9`, `10` (défaut) | ✔ | ✔ | ✔ |
| opensuse | `tumbleweed` | ✔ | ✔ | ✔ |
| arch | `latest` | ✔ | — | — |
Fedora ne construit s390x que pour la version courante, et sur une
arborescence à part (`fedora-secondary`) — d'où la version unique.
`opensuse` désigne openSUSE Tumbleweed, seule entrée dont qpdf (12.3.2)
dépasse déjà le seuil de pikepdf : la compilation de qpdf, une demi-heure, ne
s'y déclenche jamais — ce qui compte sous émulation s390x.
Fournissez un chemin d'image en argument positionnel pour surcharger
l'emplacement de téléchargement automatique.

View file

@ -95,11 +95,16 @@ Catalog, per architecture (`deploy_qemu.py` is the source of truth):
| fedora | `41`, `42` (default), `43`, `44` | ✔ | ✔ | `43` only |
| almalinux | `9` (default), `10` | ✔ | ✔ | ✔ |
| rocky | `9`, `10` (default) | ✔ | ✔ | ✔ |
| opensuse | `tumbleweed` | ✔ | ✔ | ✔ |
| arch | `latest` | ✔ | — | — |
Fedora builds s390x only for the current release, and on a separate tree
(`fedora-secondary`) — hence the single version there.
`opensuse` is openSUSE Tumbleweed, the only entry whose qpdf (12.3.2) already
clears the pikepdf threshold: the half-hour qpdf build never runs there, which
matters under s390x emulation.
Provide an explicit image path as a positional argument to override the
automatic download location.

View file

@ -113,6 +113,12 @@ ROCKY_VERSIONS: dict[str, tuple[str, str, int, str]] = {
"9": ("9", "rocky9", 2048, "20G"),
"10": ("10", "rocky10", 2048, "20G"),
}
# openSUSE Tumbleweed : rolling, donc une seule « version ». C'est la seule
# distribution du catalogue dont qpdf (12.3.2) dépasse déjà le seuil de
# pikepdf 10 — aucune compilation de qpdf, ce qui change tout sous émulation.
OPENSUSE_VERSIONS: dict[str, tuple[str, str, int, str]] = {
"tumbleweed": ("tumbleweed", "opensusetumbleweed", 2048, "20G"),
}
# Arch est en rolling release : une seule « version » (latest).
ARCH_VERSIONS: dict[str, tuple[str, str, int, str]] = {
"latest": ("latest", "archlinux", 1024, "20G"),
@ -125,6 +131,7 @@ DISTROS: dict[str, tuple[dict[str, tuple[str, str, int, str]], str]] = {
"fedora": (FEDORA_VERSIONS, "42"),
"almalinux": (ALMALINUX_VERSIONS, "9"),
"rocky": (ROCKY_VERSIONS, "10"),
"opensuse": (OPENSUSE_VERSIONS, "tumbleweed"),
"arch": (ARCH_VERSIONS, "latest"),
}
@ -134,6 +141,7 @@ ARCH_ALIASES: dict[str, dict[str, str]] = {
"fedora": {"amd64": "x86_64", "arm64": "aarch64"},
"almalinux": {"amd64": "x86_64", "arm64": "aarch64"},
"rocky": {"amd64": "x86_64", "arm64": "aarch64"},
"opensuse": {"amd64": "x86_64", "arm64": "aarch64"},
"arch": {"amd64": "x86_64", "arm64": "aarch64"},
}
@ -146,7 +154,13 @@ NON_X86_ARCHES: tuple[str, ...] = ("arm64", "s390x")
# - s390x (IBM Z) : Ubuntu seulement (Debian/Fedora : 404 ; Arch : x86/arm).
# - arm64/aarch64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud officielle
# aarch64 sur geo.mirror.pkgbuild.com).
S390X_DISTROS: tuple[str, ...] = ("ubuntu", "almalinux", "rocky", "fedora")
S390X_DISTROS: tuple[str, ...] = (
"ubuntu",
"almalinux",
"rocky",
"fedora",
"opensuse",
)
# Une distro peut ne publier qu'une PARTIE de ses versions sur une
# architecture. Déclaration POSITIVE : hors de cette table, toutes les versions
@ -173,6 +187,7 @@ ARM64_DISTROS: tuple[str, ...] = (
"fedora",
"almalinux",
"rocky",
"opensuse",
)
@ -211,6 +226,7 @@ DEBIAN_CLOUD_BASES: tuple[str, ...] = (
"https://laotzu.ftp.acc.umu.se/cdimage/cloud",
)
ALMALINUX_CLOUD_BASE = "https://repo.almalinux.org/almalinux"
OPENSUSE_BASE = "https://download.opensuse.org"
ROCKY_CLOUD_BASE = "https://dl.rockylinux.org/pub/rocky"
FEDORA_BASE = "https://download.fedoraproject.org/pub/fedora/linux/releases"
# Serveur MAÎTRE (pas de redirection MirrorManager) : repli fiable quand le
@ -287,6 +303,18 @@ def image_candidates(
f"{ROCKY_CLOUD_BASE}/{version}/images/{a}/"
f"Rocky-{version}-GenericCloud.latest.{a}.qcow2"
]
if distro == "opensuse":
# Les architectures secondaires vivent sous /ports/, et zsystems DOUBLE
# l'architecture dans le nom du fichier — irrégularité vérifiée dans
# l'index, pas déduite : « .s390x-s390x-Cloud » contre « .x86_64-Cloud ».
port = {"s390x": "ports/zsystems/", "arm64": "ports/aarch64/"}.get(
arch, ""
)
tag = f"{a}-{a}" if arch == "s390x" else a
return [
f"{OPENSUSE_BASE}/{port}tumbleweed/appliances/"
f"openSUSE-Tumbleweed-Minimal-VM.{tag}-Cloud.qcow2"
]
if distro == "arch":
# Rolling release : image « latest » officielle (cloud-init inclus).
return [f"{ARCH_CLOUD_BASE}/Arch-Linux-{a}-cloudimg.qcow2"]
@ -346,6 +374,8 @@ def default_image_name(distro: str, code: str, arch: str, version: str) -> str:
return f"debian-{version}-genericcloud-{a}.qcow2"
if distro == "arch":
return f"arch-linux-{a}-cloudimg.qcow2"
if distro == "opensuse":
return f"opensuse-tumbleweed-minimal-vm-{a}.qcow2"
if distro in ("almalinux", "rocky"):
# « latest » est MUTABLE : le nom de cache porte donc la majeure, et
# une image déjà téléchargée sera réutilisée telle quelle. C'est voulu
@ -1374,12 +1404,25 @@ def host_timezone() -> str:
return "UTC"
def admin_group(distro: str) -> str:
"""Groupe d'administration de la distribution.
def user_groups(distro: str) -> str:
"""Groupes secondaires du compte créé par cloud-init.
Debian et Ubuntu : « sudo ». Famille RHEL (AlmaLinux, Rocky, Fedora) et
Arch : « wheel » — elles n'ont aucun groupe « sudo »."""
return "sudo" if distro in ("ubuntu", "debian") else "wheel"
Le nom du groupe d'administration change d'une famille à l'autre, et un
nom INCONNU fait échouer « useradd -G » : l'utilisateur n'est alors pas
créé du tout, donc ni mot de passe ni clé SSH, et la VM démarre
inaccessible. Le privilège vient de toute façon de la directive « sudo: »
du cloud-config, pas du groupe — celui-ci n'est qu'une commodité.
- Debian, Ubuntu : « sudo ».
- Famille RHEL (AlmaLinux, Rocky, CentOS, Fedora), Arch : « wheel ».
- openSUSE : aucun. Son cloud-init par défaut n'en met pas, et rien ne
garantit « wheel » sur une image Minimal-VM — dans le doute on s'abstient
plutôt que de risquer un compte non créé."""
if distro in ("ubuntu", "debian"):
return "users, sudo"
if distro == "opensuse":
return "users"
return "users, wheel"
def build_cloud_config(
@ -1400,7 +1443,7 @@ def build_cloud_config(
# même écart, elles n'ont pas de groupe « sudo » mais « wheel ».
# Le privilège lui-même vient de la ligne « sudo: » ci-dessus, pas du
# groupe : celui-ci n'est qu'une commodité.
f" groups: users, {admin_group(args.distro)}",
f" groups: {user_groups(args.distro)}",
" shell: /bin/bash",
" lock_passwd: false" if pw_hash else " lock_passwd: true",
]

View file

@ -1027,6 +1027,7 @@ class TODO:
"fedora": (["41", "42", "43", "44"], "42"),
"almalinux": (["9", "10"], "9"),
"rocky": (["9", "10"], "10"),
"opensuse": (["tumbleweed"], "tumbleweed"),
"arch": (["latest"], "latest"),
}
@ -1074,13 +1075,20 @@ class TODO:
# Distros publiant des images cloud par architecture (cohérent avec
# S390X_DISTROS / ARM64_DISTROS de deploy_qemu.py). amd64 : toutes.
_QEMU_S390X_DISTROS = ("ubuntu", "almalinux", "rocky", "fedora")
_QEMU_S390X_DISTROS = (
"ubuntu",
"almalinux",
"rocky",
"fedora",
"opensuse",
)
_QEMU_ARM64_DISTROS = (
"ubuntu",
"debian",
"fedora",
"almalinux",
"rocky",
"opensuse",
)
# Alias distro pour l'affichage (jeton générique -> nom courant).
_QEMU_ARCH_ALIAS = {"amd64": "x86_64", "arm64": "aarch64"}
@ -4325,6 +4333,7 @@ class TODO:
"dnf_env": "graphical-server-environment "
"workstation-product-environment gnome-desktop",
"pacman": "gnome gdm",
"zypper": "patterns-gnome-gnome_basic gdm",
"service": "gdm",
},
"cinnamon": {
@ -4336,6 +4345,7 @@ class TODO:
"apt": "cinnamon-desktop-environment dbus-x11",
"dnf_env": "cinnamon-desktop",
"pacman": "cinnamon lightdm lightdm-gtk-greeter",
"zypper": "cinnamon lightdm",
"service": "lightdm",
},
}
@ -4344,6 +4354,7 @@ class TODO:
"apt": {"packages": "xrdp", "port": 3389, "client": "RDP"},
"dnf": {"packages": "xrdp", "port": 3389, "client": "RDP"},
"pacman": {"packages": "tigervnc", "port": 5901, "client": "VNC"},
"zypper": {"packages": "xrdp", "port": 3389, "client": "RDP"},
}
# Place que prend un bureau complet, annoncée dans le plan : sur une image
# cloud de 40 G, l'oublier remplit le disque en pleine installation.
@ -4408,6 +4419,44 @@ class TODO:
">/dev/null 2>&1 || true; "
)
# Miroirs openSUSE préférés, du plus proche au dernier recours. Le
# redirecteur officiel n'est PAS géographique pour cette distribution :
# mesuré depuis Montréal sur les métadonnées oss s390x (15 Mo),
# download.opensuse.org met 23,8 s — il sert depuis l'Europe — contre
# 2,7 s pour mirrors.rit.edu. Les trois familles dnf, elles, choisissent
# déjà un miroir canadien toutes seules ; rien à faire de ce côté.
#
# Chaque miroir est SONDÉ sur le chemin de l'architecture ET du produit
# courants, puis le premier qui répond gagne. C'est nécessaire : aucun ne
# réplique tout. Relevé le 2026-08-12 —
# csclub Leap oui, Tumbleweed non (404)
# rit.edu zsystems oui ; injoignable ce jour-là (curl 7)
# leaseweb Tumbleweed x86_64 et Leap oui, ports zsystems non
# D'où plusieurs entrées plutôt qu'une : avec la seule rit.edu, sa panne
# renvoyait tout le monde sur download.opensuse.org, servi d'Europe.
# Ordonnées par proximité de Montréal. Aucun sondage concluant : on garde
# les dépôts de l'image, donc le comportement d'avant.
_QEMU_ZYPPER_MIRRORS = (
"https://mirror.csclub.uwaterloo.ca/opensuse",
"https://mirrors.rit.edu/opensuse",
"https://mirror.us.leaseweb.net/opensuse",
)
def _qemu_zypper_mirror_cmd(self):
"""Réécrit l'hôte des dépôts zypper vers un miroir plus proche."""
mirrors = " ".join(self._QEMU_ZYPPER_MIRRORS)
return (
"zp=tumbleweed; "
'[ "$(uname -m)" = s390x ] && zp=ports/zsystems/tumbleweed; '
f"for zm in {mirrors}; do "
"if curl -fsS --max-time 20 -o /dev/null "
'"$zm/$zp/repo/oss/repodata/repomd.xml"; then '
"sudo sed -i "
'"s|https\\?://download\\.opensuse\\.org|$zm|g" '
"/etc/zypp/repos.d/*.repo 2>/dev/null || true; "
f'echo " {t("openSUSE mirror:")} $zm"; break; fi; done; '
)
def _qemu_desktop_remote_cmd(self, flavour="gnome"):
"""Bloc shell installant le bureau choisi + son accès distant, quelle
que soit la distribution. Même aiguillage que l'installation ERPLibre,
@ -4440,6 +4489,14 @@ class TODO:
"|| sudo rm -f /var/lib/pacman/db.lck; "
f"sudo pacman -S --needed --noconfirm {de['pacman']} "
f"{rem['pacman']['packages']}; "
"elif command -v zypper >/dev/null 2>&1; then "
"sudo zypper --non-interactive refresh || true; "
# « --auto-agree-with-licenses » appartient à la SOUS-COMMANDE
# install, pas aux options globales : placé avant, zypper répond
# « The flag --auto-agree-with-licenses is not known ».
"sudo zypper --non-interactive install "
f"--auto-agree-with-licenses {de['zypper']} "
f"{rem['zypper']['packages']}; "
"else echo 'Gestionnaire de paquets inconnu'; exit 1; fi; "
# Le bureau ne sert à rien s'il ne démarre pas tout seul : les
# images cloud démarrent en multi-user.target.
@ -4550,10 +4607,29 @@ class TODO:
"--save /etc/pacman.d/mirrorlist || true; "
"sudo pacman -Syu --noconfirm || true; "
"sudo pacman -S --needed --noconfirm $PKGS; "
"elif command -v zypper >/dev/null 2>&1; then "
# openSUSE : « --non-interactive » vaut le -y des autres, et
# « --auto-agree-with-licenses », qui va APRÈS « install »,
# évite un blocage sur une licence à accepter.
# Tumbleweed étant rolling, on rafraîchit avant d'installer.
+ self._qemu_zypper_mirror_cmd()
+ "sudo zypper --non-interactive refresh || true; "
# Tumbleweed est ROLLING et ne supporte pas les mises à jour
# partielles, exactement comme Arch. L'image cloud est un
# instantané figé : ses dépôts ont avancé depuis, et un
# « install » simple bute sur une incohérence — vécu, git 2.54
# réclamait perl-Git bâti contre un perl-base plus ancien que
# celui de l'image. zypper proposait alors trois solutions et
# attendait un choix ; « --non-interactive » prend le défaut,
# « c » = annuler, et l'installation s'arrêtait là.
"sudo zypper --non-interactive dup --auto-agree-with-licenses "
"--allow-vendor-change || true; "
"sudo zypper --non-interactive install "
"--auto-agree-with-licenses $PKGS; "
"elif command -v yum >/dev/null 2>&1; then "
"sudo yum makecache -q || true; sudo yum install -y $PKGS; "
"else echo 'Aucun gestionnaire de paquets "
"(apt/dnf/pacman/yum)'; exit 1; fi; "
"(apt/dnf/pacman/zypper/yum)'; exit 1; fi; "
# Vérifie explicitement que tout est là : erreur nette plutôt
# qu'un « command not found » cryptique plus loin.
"for t in curl git make; do command -v $t >/dev/null 2>&1 || "

View file

@ -2055,6 +2055,10 @@ TRANSLATIONS = {
"fr": "Installation du bureau GNOME (long)",
"en": "Installing the GNOME desktop (long)",
},
"openSUSE mirror:": {
"fr": "miroir openSUSE :",
"en": "openSUSE mirror:",
},
"Remote desktop:": {
"fr": "Bureau distant :",
"en": "Remote desktop:",