erplibre/script/qemu/deploy_qemu.py

1924 lines
73 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Déploiement rapide de VM Linux (Ubuntu/Debian/Fedora) via cloud-image + qemu-img + cloud-init + virt-install.
Choisissez la distribution avec --distro (ubuntu par défaut, debian, fedora)
et la version avec --version. « --list-images » affiche tout le catalogue et
les specs minimales. Reprend le workflow des notes :
1. Télécharge l'image cloud (si absente du cache -> pas de double téléchargement).
2. Convertit/copie l'image en un qcow2 de travail dédié à la VM.
3. Redimensionne le disque virtuel.
4. Génère user-data / meta-data et construit le seed.iso (cidata).
5. Lance virt-install en important le disque + le seed en CD-ROM.
Exemples
--------
# Le plus simple : image téléchargée automatiquement (chemin déduit de
# --distro/--version, mis en cache dans /var/lib/libvirt/images/iso) et
# outils manquants installés après confirmation.
sudo ./script/qemu/deploy_qemu.py --name test-vm --version 24.04 \\
--ssh-key ~/.ssh/id_ed25519.pub
# Debian 12 / Fedora 42 (mêmes options, --distro change la source d'image)
sudo ./script/qemu/deploy_qemu.py --distro debian --version 12 \\
--name deb12 --ssh-key ~/.ssh/id_ed25519.pub
sudo ./script/qemu/deploy_qemu.py --distro fedora --version 42 \\
--name fed42 --ssh-key ~/.ssh/id_ed25519.pub
# Voir tout le catalogue (distros, versions, specs minimales)
./script/qemu/deploy_qemu.py --list-images
# Télécharger (et vérifier) une image, sans créer de VM
sudo ./script/qemu/deploy_qemu.py --download-only --version 24.04 --verify
# Déploiement minimal en fournissant explicitement le chemin d'image
sudo ./qemu-deploy.py /var/lib/libvirt/images/iso/noble.img \\
--name test-vm --ssh-key ~/.ssh/id_ed25519.pub
# Reproduction fidèle des notes (8 Go RAM, 8 vCPU, mot de passe demandé)
sudo ./qemu-deploy.py /var/lib/libvirt/images/iso/noble.img \\
--name test-vm --memory 8192 --vcpus 8 --disk-size 120G --ask-password
# Voir ce qui serait fait, sans rien exécuter
./qemu-deploy.py /var/lib/libvirt/images/iso/noble.img --name test-vm --dry-run
sudo ./script/qemu/deploy_qemu.py /var/lib/libvirt/images/iso/noble.img --name test-vm --memory 8192 --vcpus 8 --disk-size 120G --ask-password --force
"""
from __future__ import annotations
import argparse
import getpass
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
import grp
import hashlib
import os
import re
import shutil
import socket
import subprocess
import sys
import tempfile
import textwrap
import time
import urllib.error
import urllib.request
import warnings
from pathlib import Path
# --------------------------------------------------------------------------- #
# Registre des distributions : version -> (code, --osinfo, RAM min Mo, disque).
# Le « code » est le nom de code (Ubuntu/Debian) ou le numéro de release
# (Fedora). RAM/disque minimaux proviennent de libosinfo (osinfo-db) : ce sont
# les seuils sous lesquels virt-install avertit. Ils servent de valeurs PAR
# DÉFAUT — la VM démarre au plus juste sans gaspiller la RAM de l'hôte.
# --codename / --osinfo / --memory / --disk-size surchargent au besoin.
# --------------------------------------------------------------------------- #
# NB : les versions intermédiaires (non-LTS) sont RETIRÉES du miroir
# cloud-images une fois EOL (leur /current/ renvoie 404). On ne garde donc
# que les LTS + les intermédiaires encore publiées. À réviser au fil du temps.
# Disque MINIMUM = 20G partout : un ERPLibre + Odoo installé occupe ~11G, et
# l'installation (caches pip, sync repo, sources Odoo) en consomme plus en
# transitoire. Un VM à 10G tombait « Poetry installation error » (disque
# plein). Le qcow2 est CREUX (sparse) : une taille virtuelle plus grande ne
# consomme rien tant qu'elle n'est pas remplie -> 20G sans surcoût réel.
UBUNTU_VERSIONS: dict[str, tuple[str, str, int, str]] = {
"20.04": ("focal", "ubuntu20.04", 2048, "20G"),
"22.04": ("jammy", "ubuntu22.04", 2048, "20G"),
"24.04": ("noble", "ubuntu24.04", 3072, "20G"),
"25.10": ("questing", "ubuntu25.10", 3072, "20G"),
"26.04": ("resolute", "ubuntu26.04", 3072, "20G"),
}
DEBIAN_VERSIONS: dict[str, tuple[str, str, int, str]] = {
"11": ("bullseye", "debian11", 1024, "20G"),
"12": ("bookworm", "debian12", 1024, "20G"),
"13": ("trixie", "debian13", 1024, "20G"),
}
FEDORA_VERSIONS: dict[str, tuple[str, str, int, str]] = {
"41": ("41", "fedora41", 2048, "20G"),
"42": ("42", "fedora42", 2048, "20G"),
"43": ("43", "fedora43", 2048, "20G"),
"44": ("44", "fedora44", 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"),
}
# distro -> (table des versions, version par défaut).
DISTROS: dict[str, tuple[dict[str, tuple[str, str, int, str]], str]] = {
"ubuntu": (UBUNTU_VERSIONS, "24.04"),
"debian": (DEBIAN_VERSIONS, "12"),
"fedora": (FEDORA_VERSIONS, "42"),
"arch": (ARCH_VERSIONS, "latest"),
}
# Traduction de l'arch générique (amd64/arm64) vers le nom propre à la distro.
# s390x s'écrit « s390x » partout (identité), donc aucune entrée n'est requise.
ARCH_ALIASES: dict[str, dict[str, str]] = {
"fedora": {"amd64": "x86_64", "arm64": "aarch64"},
"arch": {"amd64": "x86_64", "arm64": "aarch64"},
}
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
# Architectures non-x86 nécessitant une machine/un amorçage spécifiques (voir
# virt_install). Émulées en TCG (pas de KVM), donc LENTES, quand elles
# diffèrent de l'architecture de l'hôte.
NON_X86_ARCHES: tuple[str, ...] = ("arm64", "s390x")
# Distros publiant des images cloud par architecture (vérifié juillet 2026) :
# - 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",)
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
ARM64_DISTROS: tuple[str, ...] = ("ubuntu", "debian", "fedora")
# arch générique -> distros la publiant (pour valider --arch tôt).
ARCH_DISTRO_SUPPORT: dict[str, tuple[str, ...]] = {
"s390x": S390X_DISTROS,
"arm64": ARM64_DISTROS,
}
def host_arch() -> str:
"""Architecture de l'hôte en jeton générique (amd64/arm64/s390x)."""
try:
machine = os.uname().machine
except (AttributeError, OSError):
machine = ""
return {
"x86_64": "amd64",
"amd64": "amd64",
"aarch64": "arm64",
"arm64": "arm64",
"s390x": "s390x",
}.get(machine, "amd64")
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
ARCH_CLOUD_BASE = "https://geo.mirror.pkgbuild.com/images/latest"
CLOUD_IMG_BASE = "https://cloud-images.ubuntu.com"
# Debian : cloud.debian.org est un redirecteur qui, selon le réseau, peut
# renvoyer vers un miroir injoignable. On essaie donc plusieurs bases dans
# l'ordre (le premier miroir qui répond gagne). Les deux acc.umu.se sont des
# miroirs Debian officiels de repli.
DEBIAN_CLOUD_BASES: tuple[str, ...] = (
"https://cloud.debian.org/images/cloud",
"https://gemmei.ftp.acc.umu.se/cdimage/cloud",
"https://laotzu.ftp.acc.umu.se/cdimage/cloud",
)
FEDORA_BASE = "https://download.fedoraproject.org/pub/fedora/linux/releases"
# Serveur MAÎTRE (pas de redirection MirrorManager) : repli fiable quand le
# redirecteur envoie sur un miroir incomplet (fréquent en déploiement
# PARALLÈLE : chaque requête peut tomber sur un miroir différent/désynchronisé).
FEDORA_BASE_MASTER = "https://dl.fedoraproject.org/pub/fedora/linux/releases"
# Répertoire de cache par défaut des images cloud (cohérent avec --disk-dir /
# --seed-dir). L'écriture y nécessite root : le déploiement tourne de toute
# façon sous sudo (virt-install). Surchargez avec --image-dir au besoin.
DEFAULT_IMAGE_DIR = Path("/var/lib/libvirt/images/iso")
# Emplacements de la base osinfo-db (détection d'un --osinfo connu).
OSINFO_DB_DIRS: tuple[str, ...] = (
"/usr/share/osinfo/os",
"/usr/local/share/osinfo/os",
os.path.expanduser("~/.local/share/osinfo/os"),
)
def distro_arch(distro: str, arch: str) -> str:
"""Nom d'architecture attendu par la distro (Fedora utilise x86_64)."""
return ARCH_ALIASES.get(distro, {}).get(arch, arch)
def image_url(distro: str, code: str, arch: str, version: str) -> str:
"""URL directe (primaire) de l'image cloud (Ubuntu/Debian)."""
return image_candidates(distro, code, arch, version, dry_run=True)[0]
def image_candidates(
distro: str, code: str, arch: str, version: str, dry_run: bool = False
) -> list[str]:
"""Liste ordonnée d'URL candidates pour l'image cloud. Plusieurs miroirs
pour Debian ; une seule URL pour Ubuntu/Fedora."""
a = distro_arch(distro, arch)
if distro == "ubuntu":
# /releases/<version>/release/ : présent pour toutes les versions
# publiées (redirige vers l'image datée), contrairement à /current/
# qui disparaît quand une version intermédiaire devient EOL.
return [
f"{CLOUD_IMG_BASE}/releases/{version}/release/"
f"ubuntu-{version}-server-cloudimg-{a}.img"
]
if distro == "debian":
return [
f"{base}/{code}/latest/debian-{version}-genericcloud-{a}.qcow2"
for base in DEBIAN_CLOUD_BASES
]
if distro == "fedora":
return [resolve_fedora_url(version, arch, dry_run)]
if distro == "arch":
# Rolling release : image « latest » officielle (cloud-init inclus).
return [f"{ARCH_CLOUD_BASE}/Arch-Linux-{a}-cloudimg.qcow2"]
raise ValueError(f"URL indisponible pour la distro {distro!r}")
def resolve_fedora_url(version: str, arch: str, dry_run: bool) -> str:
"""Résout l'URL du qcow2 « Fedora Cloud Base Generic » depuis l'index
HTML des releases (Fedora ne publie pas de lien « latest »). ROBUSTE :
plusieurs tentatives sur le redirecteur puis repli sur le serveur maître
(dl.fedoraproject.org) sinon, en déploiement parallèle, une requête
tombée sur un miroir incomplet faisait échouer la VM (« image introuvable »)
alors que l'image existe bel et bien."""
a = distro_arch("fedora", arch)
pattern = re.compile(
rf"Fedora-Cloud-Base-Generic-{version}-[0-9.]+\.{a}\.qcow2"
)
if dry_run:
index = f"{FEDORA_BASE}/{version}/Cloud/{a}/images/"
return index + f"Fedora-Cloud-Base-Generic-{version}-<build>.{a}.qcow2"
# On essaie le redirecteur (2 fois : chaque requête peut viser un miroir
# différent) puis le serveur maître (toujours complet).
bases = [FEDORA_BASE, FEDORA_BASE, FEDORA_BASE_MASTER]
last_err = ""
for base in bases:
index = f"{base}/{version}/Cloud/{a}/images/"
try:
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
with urllib.request.urlopen(
index, timeout=30
) as resp: # noqa: S310
html = resp.read().decode(errors="replace")
except Exception as exc: # pragma: no cover - dépend du réseau
last_err = str(exc)
continue
names = sorted(set(pattern.findall(html)))
if names:
return index + names[-1]
sys.exit(
"Aucune image « Fedora-Cloud-Base-Generic » trouvée pour "
f"Fedora {version} ({a}) après plusieurs miroirs"
+ (f" (dernière erreur : {last_err})" if last_err else "")
)
def default_image_name(distro: str, code: str, arch: str, version: str) -> str:
"""Nom de fichier local pour le cache d'image."""
a = distro_arch(distro, arch)
if distro == "ubuntu":
return f"ubuntu-{version}-server-cloudimg-{a}.img"
if distro == "debian":
return f"debian-{version}-genericcloud-{a}.qcow2"
if distro == "arch":
return f"arch-linux-{a}-cloudimg.qcow2"
return f"fedora-cloud-{version}-{a}.qcow2"
def osinfo_known(short_id: str) -> bool:
"""Vrai si l'id osinfo (ex. « ubuntu26.04 ») figure dans osinfo-db."""
needle = f"<short-id>{short_id}</short-id>"
for base in OSINFO_DB_DIRS:
if not os.path.isdir(base):
continue
for root, _dirs, files in os.walk(base):
for fn in files:
if not fn.endswith(".xml"):
continue
try:
with open(
os.path.join(root, fn),
encoding="utf-8",
errors="replace",
) as fh:
if needle in fh.read():
return True
except OSError:
continue
return False
def osinfo_arg(osinfo: str, distro: str = "") -> str:
"""Valeur --osinfo résiliente à un osinfo-db périmé. Si l'id est connu on
l'utilise ; sinon on retombe sur le DERNIER osinfo connu de la même distro
(meilleures perfs que « generic », pas d'avertissement) ; en dernier
recours, détection best-effort. Utile pour une version plus récente que la
base locale (ex. ubuntu26.04, fedora43+)."""
if "=" in osinfo or "," in osinfo:
return osinfo # forme avancée déjà fournie par l'utilisateur
if osinfo_known(osinfo):
return osinfo
# Repli 1 : dernier osinfo connu de la même distro (ordre de la table).
versions = DISTROS.get(distro, ({}, ""))[0]
known = [v[1] for v in versions.values() if osinfo_known(v[1])]
if known:
fallback = known[-1]
print(
f" osinfo « {osinfo} » inconnu (osinfo-db) — repli sur "
f"« {fallback} » (proche). « sudo apt upgrade osinfo-db » pour "
"l'entrée exacte."
)
return fallback
# Repli 2 : détection best-effort (évite l'échec, peut donner generic).
print(
f" osinfo « {osinfo} » inconnu de la base locale (osinfo-db) — repli "
"sur la détection auto (detect=on,require=off).\n"
" Astuce : « sudo apt upgrade osinfo-db » pour des métadonnées à jour."
)
return "detect=on,require=off"
def list_images() -> None:
"""Affiche toutes les distros/versions et leurs specs (--list-images)."""
print("Images cloud disponibles (distro / version / specs) :\n")
for distro, (versions, default) in DISTROS.items():
print(f" {distro} (défaut : {default})")
for v, (code, osinfo, ram, disk) in versions.items():
star = "*" if v == default else " "
note = (
""
if osinfo_known(osinfo)
else " [osinfo local absent → auto]"
)
print(
f" {star} {v:<7} {code:<10} osinfo={osinfo:<12} "
f"RAM≥{ram}Mo disque≥{disk}{note}"
)
print()
print("Exemple : deploy_qemu.py --distro debian --version 12 --name vm1")
# --------------------------------------------------------------------------- #
# Utilitaires d'exécution
# --------------------------------------------------------------------------- #
class Runner:
"""Exécute (ou affiche, en dry-run) les commandes, avec sudo au besoin."""
def __init__(self, use_sudo: bool, dry_run: bool) -> None:
self.use_sudo = use_sudo
self.dry_run = dry_run
def run(
self, cmd: list[str], *, privileged: bool = False, check: bool = True
) -> None:
if privileged and self.use_sudo:
cmd = ["sudo", *cmd]
printable = " ".join(cmd)
if self.dry_run:
print(f" [dry-run] {printable}")
return
print(f" $ {printable}")
try:
subprocess.run(cmd, check=check)
except subprocess.CalledProcessError as exc:
# Sortie propre plutôt qu'une trace Python illisible.
sys.exit(
f"\nÉchec de la commande (code {exc.returncode}) :\n"
f" {printable}"
)
def need_tool(name: str) -> None:
if shutil.which(name) is None:
sys.exit(f"Erreur : outil requis introuvable dans le PATH : {name!r}")
# --------------------------------------------------------------------------- #
# Détection et installation des dépendances système
# --------------------------------------------------------------------------- #
# Outils indispensables au déploiement complet (le mode --download-only n'en
# requiert aucun).
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
# URI libvirt visée par TOUS les clients (virsh, virt-install). À ne jamais
# laisser implicite : pour un utilisateur non root, libvirt choisit
# « qemu:///session », où le réseau « default » N'EXISTE PAS — virt-install
# échoue alors sur « --network network=default » de façon incompréhensible.
# Appartenir au groupe libvirt donne le DROIT d'accéder à qemu:///system mais
# ne change PAS l'URI par défaut : il faut l'imposer.
LIBVIRT_URI = "qemu:///system"
REQUIRED_TOOLS: tuple[str, ...] = ("qemu-img", "virt-install", "virsh")
# Pour construire le seed cloud-init il faut AU MOINS un de ces outils.
SEED_TOOLS: tuple[str, ...] = ("cloud-localds", "genisoimage")
# outil -> nom de paquet, selon le gestionnaire de paquets détecté.
TOOL_PACKAGES: dict[str, dict[str, str]] = {
"apt": {
"qemu-img": "qemu-utils",
"virt-install": "virtinst",
"virsh": "libvirt-clients",
"cloud-localds": "cloud-image-utils",
"genisoimage": "genisoimage",
"openssl": "openssl",
},
"dnf": {
"qemu-img": "qemu-img",
"virt-install": "virt-install",
"virsh": "libvirt-client",
"cloud-localds": "cloud-utils",
"genisoimage": "genisoimage",
"openssl": "openssl",
},
"pacman": {
"qemu-img": "qemu-img",
"virt-install": "virt-install",
"virsh": "libvirt",
"cloud-localds": "cloud-image-utils",
"genisoimage": "cdrtools",
"openssl": "openssl",
},
"zypper": {
"qemu-img": "qemu-tools",
"virt-install": "virt-install",
"virsh": "libvirt-client",
"cloud-localds": "cloud-utils-cloud-localds",
"genisoimage": "genisoimage",
"openssl": "openssl",
},
"brew": {
"qemu-img": "qemu",
"virt-install": "virt-manager",
"virsh": "libvirt",
"cloud-localds": "cdrtools",
"genisoimage": "cdrtools",
"openssl": "openssl",
},
}
# Paquets fournissant le démon libvirt + l'émulateur QEMU système. Ils sont
# INDISPENSABLES pour exécuter la VM : virsh/virt-install (clients) seuls ne
# créent pas le socket /var/run/libvirt/libvirt-sock.
# On inclut le firmware UEFI (OVMF/edk2) : le boot par défaut est UEFI
# (indispensable pour Debian 13+ ; les images récentes n'ont plus de BIOS).
DAEMON_PACKAGES: dict[str, list[str]] = {
"apt": ["libvirt-daemon-system", "qemu-system-x86", "ovmf"],
"dnf": ["libvirt-daemon-kvm", "qemu-kvm", "edk2-ovmf"],
"pacman": ["libvirt", "qemu-desktop", "dnsmasq", "edk2-ovmf"],
"zypper": [
"libvirt-daemon",
"libvirt-daemon-qemu",
"qemu-kvm",
"qemu-ovmf-x86_64",
],
"brew": [],
}
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
# Émulateurs QEMU système pour architectures non-x86, requis pour --arch
# <arch> sur un hôte d'architecture différente (émulation TCG). Par arch puis
# par gestionnaire de paquets. NB apt : qemu-system-misc ne contient PAS s390x
# (alpha/avr/… seulement) -> paquet dédié qemu-system-s390x ; qemu-system-arm
# fournit qemu-system-aarch64 ; l'arm64 exige aussi le firmware UEFI AAVMF.
EMULATOR_PACKAGES: dict[str, dict[str, list[str]]] = {
"s390x": {
"apt": ["qemu-system-s390x"],
"dnf": ["qemu-system-s390x"],
"pacman": ["qemu-emulators-full"],
"zypper": ["qemu-s390"],
"brew": [],
},
"arm64": {
"apt": ["qemu-system-arm", "qemu-efi-aarch64"],
"dnf": ["qemu-system-aarch64", "edk2-aarch64"],
"pacman": ["qemu-emulators-full", "edk2-aarch64"],
"zypper": ["qemu-arm", "qemu-uefi-aarch64"],
"brew": [],
},
}
# Binaire émulateur par arch (détection de présence).
EMULATOR_BINARY: dict[str, str] = {
"s390x": "qemu-system-s390x",
"arm64": "qemu-system-aarch64",
}
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
# Firmwares UEFI AAVMF possibles (arm64) : au moins un doit exister.
AARCH64_FIRMWARE_PATHS: tuple[str, ...] = (
"/usr/share/AAVMF/AAVMF_CODE.fd",
"/usr/share/AAVMF/AAVMF_CODE.no-secboot.fd",
"/usr/share/edk2/aarch64/QEMU_EFI.fd",
"/usr/share/edk2/aarch64/QEMU_EFI-silent.fd",
"/usr/share/qemu-efi-aarch64/QEMU_EFI.fd",
"/usr/share/edk2-armvirt/aarch64/QEMU_EFI.fd",
)
# Gestionnaires de paquets, dans l'ordre de préférence :
# (clé TOOL_PACKAGES, binaire à détecter, commande d'installation, sudo, refresh)
PKG_MANAGERS: tuple[
tuple[str, str, list[str], bool, list[str] | None], ...
] = (
(
"apt",
"apt-get",
["apt-get", "install", "-y"],
True,
["apt-get", "update"],
),
("dnf", "dnf", ["dnf", "install", "-y"], True, None),
(
"pacman",
"pacman",
["pacman", "-S", "--needed", "--noconfirm"],
True,
None,
),
(
"zypper",
"zypper",
["zypper", "--non-interactive", "install"],
True,
None,
),
("brew", "brew", ["brew", "install"], False, None),
)
def detect_pkg_manager() -> (
tuple[str, list[str], bool, list[str] | None] | None
):
"""Retourne (clé, cmd d'install, use_sudo, cmd de refresh) ou None."""
for key, binary, install_cmd, use_sudo, refresh in PKG_MANAGERS:
if shutil.which(binary):
return key, install_cmd, use_sudo, refresh
return None
def missing_tools() -> list[str]:
"""Binaires requis absents du PATH (dont un outil de seed si aucun présent)."""
missing = [t for t in REQUIRED_TOOLS if shutil.which(t) is None]
if not any(shutil.which(t) for t in SEED_TOOLS):
missing.append(SEED_TOOLS[0]) # on installera cloud-localds
return missing
def prompt_yes_no(question: str, default: bool = True) -> bool:
"""Question oui/non. Lit /dev/tty pour rester visible même si stdout est
redirigé (cas d'un lancement depuis le menu todo)."""
suffix = " [O/n] " if default else " [o/N] "
prompt = question + suffix
try:
with open("/dev/tty", "r+") as tty:
tty.write(prompt)
tty.flush()
ans = tty.readline().strip().lower()
except OSError:
try:
ans = input(prompt).strip().lower()
except EOFError:
return default
if not ans:
return default
return ans in ("o", "oui", "y", "yes")
def daemon_missing() -> bool:
"""Vrai si le démon libvirt OU l'émulateur QEMU système est absent."""
libvirtd = shutil.which("libvirtd") or any(
os.path.exists(p) for p in ("/usr/sbin/libvirtd", "/usr/bin/libvirtd")
)
emulator = (
shutil.which("qemu-system-x86_64")
or shutil.which("qemu-system-x86")
or shutil.which("kvm")
)
return not (libvirtd and emulator)
def libvirt_ready(use_sudo: bool) -> bool:
"""Vrai si l'hyperviseur qemu:///system répond (démon libvirt démarré)."""
if shutil.which("virsh") is None:
return False
cmd = (["sudo"] if use_sudo else []) + [
"virsh",
"-c",
"qemu:///system",
"version",
]
try:
res = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
return res.returncode == 0
except (OSError, subprocess.SubprocessError):
return False
def ensure_libvirt_service(runner: Runner) -> None:
"""S'assure que le démon libvirt tourne ; sinon le démarre puis vérifie."""
if libvirt_ready(runner.use_sudo):
return
if shutil.which("systemctl"):
print(
" Démarrage du démon libvirt"
" (systemctl enable --now libvirtd)…"
)
runner.run(
["systemctl", "enable", "--now", "libvirtd"],
privileged=True,
check=False,
)
runner.run(
["systemctl", "start", "libvirtd.socket"],
privileged=True,
check=False,
)
# Le socket peut mettre un instant à apparaître.
for _ in range(10):
if libvirt_ready(runner.use_sudo):
return
time.sleep(1)
sys.exit(
"Erreur : impossible de se connecter à l'hyperviseur libvirt"
" (/var/run/libvirt/libvirt-sock).\n"
" Le démon libvirtd n'est pas démarré. Essayez :\n"
" sudo systemctl enable --now libvirtd\n"
" puis vérifiez : sudo virsh -c qemu:///system version"
)
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
def invoking_user() -> str:
"""Utilisateur réel derrière l'appel, même sous sudo."""
return os.environ.get("SUDO_USER") or getpass.getuser()
def ensure_libvirt_group(runner: Runner) -> bool:
"""Ajoute l'utilisateur au groupe libvirt. Renvoie True s'il l'était déjà.
SANS ce groupe, les clients libvirt d'un utilisateur non root retombent sur
« qemu:///session », le réseau « default » N'EXISTE PAS : virt-install
échoue alors sur « --network network=default » alors que tous les paquets
sont pourtant installés. C'est le trou qui manquait au profil
« ERPLibre Déploiement » : il installait QEMU sans jamais rendre
qemu:///system accessible.
"""
user = invoking_user()
if user == "root":
return True
lst_group = []
for group in ("libvirt", "kvm"):
try:
grp.getgrnam(group)
except KeyError:
continue # groupe absent sur cette distro
try:
if user in grp.getgrnam(group).gr_mem:
continue
except KeyError:
pass
lst_group.append(group)
if not lst_group:
print(f" Utilisateur « {user} » déjà dans les groupes libvirt/kvm.")
return True
for group in lst_group:
print(f" Ajout de « {user} » au groupe « {group} »…")
runner.run(
["usermod", "-aG", group, user], privileged=True, check=False
)
return False
def kernel_modules_stale() -> str:
"""Renvoie un message si les modules du noyau EN COURS ont disparu.
Sur une distro roulante, « make install_os » met le noyau à jour et le
gestionnaire de paquets SUPPRIME /lib/modules/<noyau en cours>. Tant qu'on
n'a pas redémarré, « modprobe bridge » échoue et libvirt ne peut pas créer
virbr0 : « Unable to create bridge virbr0: Package not installed ». Aucun
paquet ne corrige ça, seul un redémarrage le fait d'où ce diagnostic.
"""
if not shutil.which("uname"):
return ""
running = os.uname().release
path_module = f"/lib/modules/{running}"
if os.path.isdir(path_module):
return ""
return (
f"Le noyau en cours ({running}) n'a plus ses modules "
f"({path_module} absent) : il a été mis à jour depuis le démarrage.\n"
" libvirt ne pourra pas créer le pont virbr0 tant que la machine\n"
" n'aura pas REDÉMARRÉ. Redémarrez, puis relancez --setup-host."
)
def setup_host(runner: Runner, assume_yes: bool, no_install: bool) -> None:
"""Prépare l'hôte à faire tourner des VM : paquets, démon, groupe, réseau.
Point d'entrée unique du profil d'installation « ERPLibre Déploiement ».
Il réutilise les mêmes fonctions que le déploiement, donc les noms de
paquets restent définis à UN SEUL endroit (TOOL_PACKAGES /
DAEMON_PACKAGES) et restent valides pour apt, dnf, pacman, zypper et brew.
"""
print("\n== Préparation de l'hôte pour QEMU/libvirt ==")
# ensure_tools installe les clients ET, si le démon manque, les paquets de
# DAEMON_PACKAGES (qemu système + libvirt + firmware UEFI), puis démarre le
# service. ensure_emulator ne sert QU'À l'émulation croisée (arm64 sur x86)
# et n'a pas de clé pour l'architecture hôte.
ensure_tools(runner, assume_yes, no_install, force_daemon=True)
ensure_libvirt_service(runner)
already_in_group = ensure_libvirt_group(runner)
# Diagnostiqué AVANT le réseau : sans les modules du noyau en cours, le
# pont virbr0 est impossible et l'erreur de virsh est indéchiffrable.
stale = kernel_modules_stale()
if stale:
print(f"\n{stale}")
ensure_network("default", runner)
if runner.dry_run:
print("\n[dry-run] Rien n'a été modifié, vérification ignorée.")
return
print("\n== Vérification ==")
ok = libvirt_ready(runner.use_sudo)
print(f" hyperviseur qemu:///system : {'OK' if ok else 'INJOIGNABLE'}")
active, autostart = network_state("default", runner.use_sudo)
print(
f" réseau libvirt « default » : "
f"{'actif' if active else 'INACTIF'}"
f" / {'autostart' if autostart else 'PAS autostart'}"
)
if not (ok and active):
sys.exit(
"Erreur : l'hôte n'est pas prêt.\n"
+ (f" {stale}\n" if stale else "")
+ " Sinon vérifiez :\n"
" sudo systemctl enable --now libvirtd\n"
" sudo virsh -c qemu:///system net-start default"
)
if not already_in_group:
print(
f"\n⚠ « {invoking_user()} » vient d'être ajouté au groupe libvirt."
" Les groupes ne s'appliquent qu'aux NOUVELLES sessions :"
" reconnectez-vous (ou « newgrp libvirt »), sinon virt-install"
" continuera d'utiliser qemu:///session et le réseau « default »"
" restera introuvable."
)
print("\nHôte prêt.")
def ensure_tools(
runner: Runner,
assume_yes: bool,
no_install: bool,
force_daemon: bool = False,
) -> None:
"""Vérifie outils, démon libvirt et émulateur ; installe/démarre ce qui
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
manque, puis vérifie la connexion à l'hyperviseur.
`force_daemon` réinstalle DAEMON_PACKAGES même quand le démon répond déjà.
Vécu sur Arch : libvirt était présent (posé par un ancien one-liner qui ne
listait pas dnsmasq), donc daemon_missing() renvoyait False et dnsmasq
n'était jamais installé -> « Failed to start network default ». Les
gestionnaires de paquets ignorent ce qui est déjà : c'est bon marché.
"""
missing = missing_tools()
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
need_daemon = daemon_missing() or force_daemon
# Tout est là et l'hyperviseur répond : rien à faire.
if not missing and not need_daemon and libvirt_ready(runner.use_sudo):
return
pm = detect_pkg_manager()
if pm is None:
sys.exit(
" Gestionnaire de paquets non reconnu "
"(apt/dnf/pacman/zypper/brew).\n"
" Installez manuellement : " + ", ".join(missing)
)
pm_key, install_cmd, use_sudo, refresh = pm
pkg_map = TOOL_PACKAGES[pm_key]
packages = [pkg_map.get(t, t) for t in missing]
if need_daemon:
packages += DAEMON_PACKAGES.get(pm_key, [])
packages = list(dict.fromkeys(packages)) # dédoublonne, ordre gardé
if packages:
label = list(missing)
if need_daemon:
label.append("démon libvirt / émulateur QEMU")
print(" Composants manquants : " + ", ".join(label))
if no_install:
sys.exit(
" Installation automatique désactivée (--no-install-deps).\n"
" Installez manuellement : " + " ".join(packages)
)
full_cmd = install_cmd + packages
printable = " ".join(full_cmd)
if use_sudo and runner.use_sudo:
printable = "sudo " + printable
print(f" Gestionnaire détecté : {pm_key}")
print(f" Commande d'installation : {printable}")
if not assume_yes and not prompt_yes_no(
" Installer ces dépendances maintenant ?"
):
sys.exit(
" Installation refusée.\n Commande manuelle : " + printable
)
if refresh:
runner.run(refresh, privileged=use_sudo, check=False)
runner.run(full_cmd, privileged=use_sudo)
still = missing_tools()
# Repli : si seul l'outil de seed manque encore (le nom du paquet
# cloud-localds varie selon la distro), tente genisoimage.
if still == [SEED_TOOLS[0]]:
alt = pkg_map.get("genisoimage", "genisoimage")
print(f" Repli sur {alt} (autre outil de seed cloud-init)…")
runner.run(install_cmd + [alt], privileged=use_sudo, check=False)
still = missing_tools()
if still:
sys.exit(
" Outils toujours absents après installation : "
+ ", ".join(still)
+ f"\n Essayez manuellement : {printable}"
)
print(" Dépendances installées avec succès.")
# Démarre le démon (si nécessaire) et vérifie l'accès à l'hyperviseur.
ensure_libvirt_service(runner)
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
def emulator_ready(arch: str) -> bool:
"""Vrai si l'émulateur (et, pour arm64, un firmware UEFI AAVMF) est là."""
if shutil.which(EMULATOR_BINARY[arch]) is None:
return False
if arch == "arm64":
return any(os.path.exists(p) for p in AARCH64_FIRMWARE_PATHS)
return True
def ensure_emulator(
arch: str, runner: Runner, assume_yes: bool, no_install: bool
) -> None:
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
"""Vérifie l'émulateur QEMU système pour `arch` (et le firmware UEFI pour
arm64), requis quand on émule une architecture différente de l'hôte ;
l'installe au besoin via le gestionnaire de paquets."""
binary = EMULATOR_BINARY[arch]
if emulator_ready(arch):
return
pm = detect_pkg_manager()
if pm is None:
sys.exit(
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
f" Émulateur {arch} introuvable ({binary}) et gestionnaire de "
"paquets non reconnu.\n Installez-le manuellement."
)
pm_key, install_cmd, use_sudo, refresh = pm
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
packages = EMULATOR_PACKAGES.get(arch, {}).get(pm_key, [])
if not packages:
sys.exit(
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
f" Émulateur {arch} introuvable ({binary}) : installez le paquet "
"QEMU système correspondant de votre distribution "
"(+ firmware UEFI pour arm64)."
)
full_cmd = install_cmd + packages
printable = ("sudo " if use_sudo and runner.use_sudo else "") + " ".join(
full_cmd
)
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
print(f" Émulateur {arch} manquant ({binary}).")
print(f" Commande d'installation : {printable}")
if no_install:
sys.exit(
" Installation automatique désactivée (--no-install-deps).\n"
" Installez manuellement : " + printable
)
if not assume_yes and not prompt_yes_no(
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
f" Installer l'émulateur {arch} maintenant ?"
):
sys.exit(" Installation refusée.\n Commande manuelle : " + printable)
if refresh:
runner.run(refresh, privileged=use_sudo, check=False)
runner.run(full_cmd, privileged=use_sudo)
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
if not emulator_ready(arch):
sys.exit(
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
f" Émulateur {arch} ({binary}) ou firmware toujours absent après "
f"installation.\n Essayez manuellement : {printable}"
)
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
print(f" Émulateur {arch} installé avec succès.")
# --------------------------------------------------------------------------- #
# Étapes
# --------------------------------------------------------------------------- #
# Délai (s) par opération réseau : au-delà, on abandonne le miroir courant.
# Sans lui, un miroir injoignable ferait pendre le téléchargement à l'infini.
DOWNLOAD_TIMEOUT = 30
def _download_one(url: str, tmp: Path, timeout: int) -> None:
"""Télécharge url -> tmp en streaming, avec timeout et barre de %.
Lève une exception en cas d'échec réseau (miroir suivant à essayer)."""
is_tty = sys.stdout.isatty()
last_pct = -1
req = urllib.request.Request(
url, headers={"User-Agent": "erplibre-qemu-deploy"}
)
with urllib.request.urlopen(req, timeout=timeout) as resp: # noqa: S310
total = int(resp.headers.get("Content-Length", 0) or 0)
done = 0
with open(tmp, "wb") as fh:
while True:
chunk = resp.read(1 << 16)
if not chunk:
break
fh.write(chunk)
done += len(chunk)
if total <= 0:
continue
pct = min(100, done * 100 // total)
if pct == last_pct:
continue
last_pct = pct
# TTY : une ligne réécrite (\r) ; sinon (capturé par le menu
# todo) au plus 101 lignes, jamais de flot infini.
if is_tty:
print(f"\r {pct:3d}%", end="", flush=True)
else:
print(f" {pct:3d}%", flush=True)
if is_tty:
print()
# Vérifie la COMPLÉTUDE : si le serveur a annoncé une taille et qu'on a
# reçu moins (connexion coupée), le .part est TRONQUÉ. Sans ce contrôle,
# il était validé comme « complet » -> qcow2 valide mais VIDE (juste
# l'en-tête) -> VM qui ne boote pas, et cache empoisonné réutilisé ensuite.
if total > 0 and done < total:
raise OSError(
f"téléchargement incomplet : {done}/{total} octets reçus "
"(connexion interrompue)"
)
def download_image(
urls, dest: Path, dry_run: bool, timeout: int = DOWNLOAD_TIMEOUT
) -> None:
"""Télécharge l'image (essaie chaque miroir dans l'ordre) si absente du
cache. Échoue proprement jamais de blocage infini grâce au timeout."""
if isinstance(urls, str):
urls = [urls]
if dest.exists() and dest.stat().st_size > 0:
size_mb = dest.stat().st_size / 1024 / 1024
print(
f" Image déjà présente ({size_mb:.0f} Mo), téléchargement"
f" ignoré : {dest}",
flush=True,
)
return
if dry_run:
print(f" [dry-run] téléchargement {urls[0]} -> {dest}", flush=True)
return
try:
dest.parent.mkdir(parents=True, exist_ok=True)
except PermissionError:
sys.exit(
f"\nPermission refusée pour écrire dans {dest.parent}.\n"
" Relancez avec sudo, ou choisissez --image-dir vers un dossier"
" accessible en écriture."
)
tmp = dest.with_suffix(dest.suffix + ".part")
errors = []
had_404 = False
for i, url in enumerate(urls, 1):
tag = "" if len(urls) == 1 else f" (miroir {i}/{len(urls)})"
print(f" Téléchargement{tag} : {url}", flush=True)
try:
_download_one(url, tmp, timeout)
tmp.replace(dest)
return
except urllib.error.HTTPError as exc: # image absente sur ce miroir
tmp.unlink(missing_ok=True)
had_404 = had_404 or exc.code == 404
print(f"\n Échec : HTTP {exc.code}", flush=True)
errors.append(f"{url} -> HTTP {exc.code}")
except Exception as exc: # réseau/timeout : miroir suivant
tmp.unlink(missing_ok=True)
print(f"\n Échec : {exc}", flush=True)
errors.append(f"{url} -> {exc}")
hint = (
"\n Image introuvable (404) : cette version est probablement EOL et"
" a été retirée du miroir. Choisissez une version LTS encore"
" supportée (voir --list-images)."
if had_404
else "\n Vérifiez la connectivité (IPv6 ?), réessayez plus tard, ou"
" fournissez un chemin d'image local en argument positionnel."
)
# Chemin visé + commande prête à copier pour reprendre le téléchargement
# manuellement (reprise si un .part existe), puis relancer le déploiement
# (l'image complète en cache sera réutilisée). curl -C - reprend, -f
# échoue proprement sur une erreur HTTP.
resume = (
f"\n Destination : {dest}"
f"\n Reprendre le téléchargement manuellement :"
f"\n sudo curl -fL -C - -o {dest} \\\n {urls[0]}"
"\n puis relancez le déploiement (l'image en cache sera réutilisée)."
)
sys.exit(
"\nÉchec du téléchargement depuis tous les miroirs :\n "
+ "\n ".join(errors)
+ hint
+ resume
)
def verify_sha256(url: str, image: Path, dry_run: bool) -> None:
"""Vérifie l'empreinte via le SHA256SUMS publié dans le même répertoire."""
if dry_run:
print(" [dry-run] vérification SHA256 ignorée")
return
sums_url = url.rsplit("/", 1)[0] + "/SHA256SUMS"
filename = url.rsplit("/", 1)[1]
print(f" Vérification SHA256 via {sums_url}")
try:
with urllib.request.urlopen( # noqa: S310
sums_url, timeout=DOWNLOAD_TIMEOUT
) as resp:
sums = resp.read().decode()
except Exception as exc: # pragma: no cover
sys.exit(f"Impossible de récupérer SHA256SUMS : {exc}")
expected = next(
(
line.split()[0]
for line in sums.splitlines()
if line.strip().endswith(filename)
),
None,
)
if expected is None:
sys.exit(f"Empreinte introuvable pour {filename} dans SHA256SUMS")
h = hashlib.sha256()
with image.open("rb") as fh:
for chunk in iter(lambda: fh.read(1 << 20), b""):
h.update(chunk)
if h.hexdigest() != expected:
image.unlink(
missing_ok=True
) # évite la réutilisation du cache corrompu
sys.exit(
f"SHA256 NON conforme ! Image supprimée : {image}\n"
f" attendu : {expected}\n obtenu : {h.hexdigest()}"
)
print(" SHA256 conforme.")
def hash_password(plain: str) -> str:
"""Hash SHA-512 crypt ($6$...) via crypt (< 3.13) ou openssl en repli."""
try:
with warnings.catch_warnings():
# crypt est déprécié (retiré en 3.13) : on masque l'avertissement,
# le repli openssl couvre les versions récentes de Python.
warnings.simplefilter("ignore", DeprecationWarning)
import crypt
return crypt.crypt(plain, crypt.mksalt(crypt.METHOD_SHA512))
except (ImportError, AttributeError):
need_tool("openssl")
out = subprocess.run(
["openssl", "passwd", "-6", "-stdin"],
input=plain + "\n",
capture_output=True,
text=True,
check=True,
)
return out.stdout.strip()
def build_cloud_config(
args: argparse.Namespace, pw_hash: str | None, ssh_keys: list[str]
) -> str:
"""Construit le contenu #cloud-config (user-data)."""
lines: list[str] = ["#cloud-config", f"hostname: {args.hostname}"]
user_block = [
"users:",
f" - name: {args.user}",
" sudo: ALL=(ALL) NOPASSWD:ALL",
# « sudo » existe sur Debian ET Ubuntu ; « admin » n'existe PAS sur
# Debian -> useradd -G ...,admin échoue et l'utilisateur n'est jamais
# créé (login/clé SSH KO). C'était la cause du « Debian ne marche pas ».
" groups: users, sudo",
" shell: /bin/bash",
" lock_passwd: false" if pw_hash else " lock_passwd: true",
]
if pw_hash:
user_block.append(f' passwd: "{pw_hash}"')
if ssh_keys:
user_block.append(" ssh_authorized_keys:")
user_block += [f" - {k}" for k in ssh_keys]
lines += user_block
lines.append(f"ssh_pwauth: {'true' if pw_hash else 'false'}")
lines.append(f"locale: {args.locale}")
lines += [
"keyboard:",
f" layout: {args.keyboard_layout}",
f" variant: {args.keyboard_variant}",
]
# apt update/upgrade désactivés par défaut : sur un réseau lent/instable
# ils font pendre cloud-init au 1er boot (et retardent la dispo SSH). SSH
# est déjà présent dans les images cloud ; on l'active via runcmd sans apt.
# --apt-update réactive apt update (+ upgrade, sauf --no-upgrade).
do_update = args.apt_update
do_upgrade = args.apt_update and not args.no_upgrade
lines.append(f"package_update: {'true' if do_update else 'false'}")
lines.append(f"package_upgrade: {'true' if do_upgrade else 'false'}")
# On n'installe AUCUN paquet via cloud-init : cela exige le réseau au 1er
# boot et peut bloquer longtemps (dnf/apt/pacman lents sur réseau lent) —
# cloud-init reste alors « running » et tout ce qui suit attend. sshd est
# DÉJÀ présent dans toutes les images cloud (Ubuntu/Debian/Fedora/Arch) ;
# on l'active seulement (runcmd). Les outils nécessaires (curl/git/make…)
# sont installés — avec dépôts optimisés — par le bootstrap d'installation.
packages = list(dict.fromkeys(args.package)) # seulement --package
if packages:
lines.append("packages:")
lines += [f" - {p}" for p in packages]
# Active et démarre SSH quel que soit le nom du service (ssh sur
# Debian/Ubuntu, sshd sur Fedora/Arch) — sans quoi la VM peut booter
# sans SSH accessible.
lines += [
"runcmd:",
" - systemctl enable --now ssh 2>/dev/null"
" || systemctl enable --now sshd 2>/dev/null || true",
# qemu-guest-agent : installé + activé APRÈS sshd (donc SSH reste
# disponible tout de suite, sans attendre le réseau). Tout est
# « || true » : si l'installation échoue (réseau lent/absent), le boot
# n'est pas bloqué. La plupart des images cloud l'incluent déjà.
# Rafraîchit d'abord l'index (les images cloud n'ont PAS de listes apt
# -> sinon « Unable to locate package »), puis installe. timeout : un
# miroir lent ne bloque JAMAIS cloud-init. Non fatal (|| true).
# Sous-shell ( ) et NON accolades { } : « { » est un indicateur YAML.
" - (command -v apt-get >/dev/null && (timeout 120 apt-get update -qq"
" || true; timeout 300 apt-get install -y qemu-guest-agent)) ||"
" (command -v dnf >/dev/null && timeout 300 dnf install -y"
" qemu-guest-agent) || (command -v pacman >/dev/null && timeout 300"
" pacman -Sy --noconfirm qemu-guest-agent) || true",
# Autorise guest-exec (bloqué par défaut sur certaines distros).
# On VIDE la liste de blocage (block-rpcs/blacklist) plutôt que
# d'utiliser allow-rpcs (liste BLANCHE : casserait les autres RPC).
# Les deux clés couvrent les versions anciennes (blacklist) et
# récentes (block-rpcs) de qemu-ga.
" - mkdir -p /etc/qemu && printf '[general]\\nblock-rpcs=\\n"
"blacklist=\\n' > /etc/qemu/qemu-ga.conf || true",
# Fedora/RHEL bloquent guest-exec via /etc/sysconfig/qemu-ga.
# « test -f » et NON « [ -f … ] » : en YAML, « - [ » démarre une
# séquence en flux -> user-data invalide -> cloud-init rejeté (aucun
# utilisateur créé, VM figée, login console impossible).
" - test -f /etc/sysconfig/qemu-ga && sed -i"
" 's/^BLACKLIST_RPC=.*/BLACKLIST_RPC=/'"
" /etc/sysconfig/qemu-ga || true",
" - systemctl enable qemu-guest-agent 2>/dev/null"
" || systemctl enable qemu-ga 2>/dev/null || true",
# restart (et non enable --now) : recharge la config si l'agent était
# déjà démarré par l'image cloud.
" - systemctl restart qemu-guest-agent 2>/dev/null"
" || systemctl restart qemu-ga 2>/dev/null || true",
]
return "\n".join(lines) + "\n"
# network-config (cloud-init v2) : DHCP sur toute interface « e* ». Les images
# Debian genericcloud ne configurent pas toujours le réseau sans ça (le NIC
# reste down -> pas d'IP), contrairement à Ubuntu. Inoffensif pour Ubuntu.
# La clé est « eth0 » car le renderer cloud-init d'Arch utilise la CLÉ comme
# nom d'interface (en ignorant « match ») et l'image Arch nomme son NIC eth0 ;
# Debian/Fedora/Ubuntu utilisent bien « match: name: e* » (leur en*).
NETWORK_CONFIG = (
"version: 2\n"
"ethernets:\n"
" eth0:\n"
" match:\n"
' name: "e*"\n'
" dhcp4: true\n"
" dhcp6: false\n"
)
def build_seed(
cloud_cfg: str, hostname: str, seed_dest: Path, runner: Runner
) -> None:
"""Génère le seed.iso (cidata) et le copie vers seed_dest."""
meta_data = f"instance-id: {hostname}\nlocal-hostname: {hostname}\n"
with tempfile.TemporaryDirectory() as tmp:
tmp_path = Path(tmp)
ud = tmp_path / "user-data"
md = tmp_path / "meta-data"
nc = tmp_path / "network-config"
local_iso = tmp_path / "seed.iso"
if runner.dry_run:
print(" [dry-run] user-data qui serait généré :")
print(textwrap.indent(cloud_cfg, " "))
print(" [dry-run] network-config :")
print(textwrap.indent(NETWORK_CONFIG, " "))
else:
ud.write_text(cloud_cfg)
md.write_text(meta_data)
nc.write_text(NETWORK_CONFIG)
if runner.dry_run or shutil.which("cloud-localds"):
runner.run(
[
"cloud-localds",
"--network-config",
str(nc),
str(local_iso),
str(ud),
str(md),
]
)
else:
need_tool("genisoimage")
runner.run(
[
"genisoimage",
"-output",
str(local_iso),
"-volid",
"cidata",
"-joliet",
"-rock",
str(ud),
str(md),
str(nc),
]
)
(
seed_dest.parent.mkdir(parents=True, exist_ok=True)
if not runner.dry_run
else None
)
runner.run(["cp", str(local_iso), str(seed_dest)], privileged=True)
def prepare_disk(
image: Path, disk: Path, size: str, runner: Runner, force: bool
) -> None:
"""Convertit l'image cloud en qcow2 de travail puis redimensionne."""
if disk.exists() and not force:
sys.exit(
f"Le disque {disk} existe déjà. Utilisez --force pour l'écraser."
)
runner.run(
[
"qemu-img",
"convert",
"-f",
"qcow2",
"-O",
"qcow2",
str(image),
str(disk),
],
privileged=True,
)
runner.run(["qemu-img", "resize", str(disk), size], privileged=True)
def network_name(network_arg: str) -> str | None:
"""Extrait NAME de « network=NAME,... » ; None si c'est un bridge, etc."""
for part in network_arg.split(","):
if part.strip().startswith("network="):
return part.split("=", 1)[1].strip()
return None
def network_state(name: str, use_sudo: bool) -> tuple[bool, bool]:
"""(actif, autostart) d'un réseau libvirt, via « virsh net-info »."""
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
cmd = (["sudo"] if use_sudo else []) + [
"virsh",
"-c",
LIBVIRT_URI,
"net-info",
name,
]
try:
res = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
except (OSError, subprocess.SubprocessError):
return (False, False)
active = bool(re.search(r"Active:\s*yes", res.stdout, re.IGNORECASE))
autostart = bool(re.search(r"Autostart:\s*yes", res.stdout, re.IGNORECASE))
return (active, autostart)
def ensure_network(name: str | None, runner: Runner) -> None:
"""Active le réseau libvirt si besoin. On vérifie d'abord son état pour
éviter le faux « error: network is already active » de virsh quand il
tourne déjà (message purement bruyant, sans conséquence)."""
if not name:
return
if runner.dry_run:
print(f" [dry-run] réseau libvirt '{name}' activé si nécessaire")
runner.run(
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
["virsh", "-c", LIBVIRT_URI, "net-start", name],
privileged=True,
check=False,
)
runner.run(
["virsh", "-c", LIBVIRT_URI, "net-autostart", name],
privileged=True,
check=False,
)
return
active, autostart = network_state(name, runner.use_sudo)
if active and autostart:
print(f" Réseau libvirt '{name}' déjà actif.")
return
print(f" Configuration du réseau libvirt '{name}'")
if not active:
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
runner.run(
["virsh", "-c", LIBVIRT_URI, "net-start", name],
privileged=True,
check=False,
)
if not autostart:
runner.run(
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
["virsh", "-c", LIBVIRT_URI, "net-autostart", name],
privileged=True,
check=False,
)
def virt_install(
args: argparse.Namespace,
disk: Path,
seed: Path,
osinfo: str,
runner: Runner,
) -> None:
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
# Émulée (TCG, pas de KVM) si l'arch demandée diffère de celle de l'hôte.
emulated = args.arch != host_arch()
# s390x n'a pas de port série ISA : la console est SCLP (ttysclp0), et non
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
# ttyS0. Ailleurs (x86/arm64), console série classique.
console_target = "sclp" if args.arch == "s390x" else "serial"
cmd = [
"virt-install",
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
# Sans --connect, un utilisateur non root vise qemu:///session : le
# réseau « default » n'y existe pas et la création échoue même quand
# tous les paquets sont installés et le réseau actif côté système.
"--connect",
LIBVIRT_URI,
"--name",
args.name,
"--memory",
str(args.memory),
"--vcpus",
str(args.vcpus),
"--import",
"--disk",
f"path={disk},format=qcow2,bus=virtio",
# Seed cloud-init attaché comme DISQUE virtio en lecture seule (et non
# en CD-ROM) : le pilote virtio-blk est dans l'initramfs, donc le
# volume « cidata » est visible dès init-local et cloud-init le lit.
# En CD-ROM, l'initramfs Debian ne charge pas sr_mod à temps -> le
# seed n'est pas vu et rien ne s'applique (Ubuntu, lui, tolère le CD).
# Sur s390x, bus=virtio est mappé en virtio-ccw par libvirt.
"--disk",
f"path={seed},readonly=on,bus=virtio",
"--osinfo",
osinfo,
"--network",
args.network,
"--graphics",
args.graphics,
"--console",
f"pty,target_type={console_target}",
# Canal virtio de l'agent invité (org.qemu.guest_agent.0) : permet à
# virsh de piloter la VM SANS réseau (ex. étendre le FS invité après
# un redimensionnement de disque). Inoffensif si l'agent est absent.
"--channel",
"unix,target.type=virtio,target.name=org.qemu.guest_agent.0",
]
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
if args.arch == "s390x":
# s390x (IBM Z) : machine s390-ccw-virtio, amorçage IPL/zipl depuis le
# disque (ni BIOS ni UEFI/OVMF -> aucun --boot).
cmd += ["--arch", "s390x", "--machine", "s390-ccw-virtio"]
elif args.arch == "arm64":
# arm64/aarch64 : machine « virt » + UEFI (firmware AAVMF). Les images
# cloud arm64 n'ont pas de BIOS -> UEFI obligatoire. Secure Boot
# DÉSACTIVÉ (comme x86) : sinon libvirt sélectionne l'AAVMF « secure »
# à clés Microsoft enrôlées et la VM RESTE FIGÉE au firmware (aucun
# boot, aucune IP). secure-boot=no -> AAVMF non-SB, boot OK.
cmd += [
"--arch",
"aarch64",
"--machine",
"virt",
"--boot",
"uefi,firmware.feature0.name=secure-boot,"
"firmware.feature0.enabled=no",
]
elif not args.bios:
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
# Boot UEFI par défaut (x86) : Debian 13 (trixie) et les images cloud
# récentes n'embarquent plus le chargeur BIOS/GRUB-pc et partent en
# boucle « Booting... » en SeaBIOS. UEFI (OVMF) fonctionne pour
# Ubuntu/Debian/Fedora. --bios force l'ancien BIOS si OVMF est absent.
# Secure Boot DÉSACTIVÉ : le chargeur d'Arch (GRUB) n'est pas signé et
# OVMF Secure Boot le refuse (« Access Denied » -> pas de boot).
# Ubuntu/Debian/Fedora bootent aussi sans Secure Boot.
cmd += [
"--boot",
"uefi,firmware.feature0.name=secure-boot,"
"firmware.feature0.enabled=no",
]
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
if emulated:
# Architecture différente de l'hôte -> émulation logicielle TCG
# (pas de KVM). LENT.
cmd += ["--virt-type", "qemu"]
if not args.attach_console:
cmd.append("--noautoconsole")
# virtinst écrit un journal de debug dans ~/.cache/virt-manager ; sous
# sudo, HOME/cache peut être inaccessible -> l'écriture échoue et Python
# déverse un « Logging error » (le pavé « Fetched capabilities … »). On
# force un cache/HOME ÉCRIVABLE via « env VAR=… » (traverse sudo) pour
# que le journal s'écrive silencieusement.
log_env = [
"env",
"XDG_CACHE_HOME=/var/tmp/erplibre-virtinst",
"HOME=/var/tmp/erplibre-virtinst",
]
runner.run(log_env + cmd, privileged=True)
def _ip_reachable(ip: str, port: int = 22, timeout: float = 3) -> bool:
"""Vrai si le port SSH répond (distingue le bail actif du bail périmé)."""
try:
with socket.create_connection((ip, port), timeout=timeout):
return True
except OSError:
return False
def wait_for_ip(name: str, use_sudo: bool, timeout: int) -> str | None:
"""Interroge les baux DHCP libvirt jusqu'à obtenir l'IPv4 de la VM. Une VM
peut avoir PLUSIEURS baux (l'image demande d'abord une IP avec le hostname
par défaut « ubuntu », puis cloud-init fixe le vrai hostname -> 2e bail) :
on renvoie une IP JOIGNABLE (sshd up), sinon la plus récente, jamais
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
aveuglément la 1re (souvent le bail précoce périmé, « No route to host »).
"""
base = (["sudo"] if use_sudo else []) + [
"virsh",
"domifaddr",
name,
"--source",
"lease",
]
deadline = time.time() + timeout
ips: list[str] = []
while time.time() < deadline:
res = subprocess.run(base, capture_output=True, text=True)
ips = re.findall(r"(\d+\.\d+\.\d+\.\d+)", res.stdout)
for ip in ips:
if _ip_reachable(ip):
return ip
time.sleep(3)
return ips[-1] if ips else None
def ssh_command(user: str, ip: str, has_key: bool) -> str:
"""Commande SSH adaptée : clé -> connexion simple ; mot de passe seul ->
force le mot de passe pour éviter « Too many authentication failures »."""
if has_key:
return f"ssh {user}@{ip}"
return (
f"ssh -o IdentitiesOnly=yes -o PreferredAuthentications=password "
f"{user}@{ip}"
)
# --------------------------------------------------------------------------- #
# CLI
# --------------------------------------------------------------------------- #
def build_parser() -> argparse.ArgumentParser:
versions_help = "\n".join(
f" {distro:<7} {default:<7} (défaut) versions : "
+ ", ".join(versions)
for distro, (versions, default) in DISTROS.items()
)
p = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=__doc__,
epilog="Distros et versions (--distro / --version), specs via "
"--list-images :\n" + versions_help,
)
p.add_argument(
"image_path",
type=Path,
nargs="?",
default=None,
help="Chemin de cache de l'image cloud. Optionnel : si absent, il est "
"déduit de --distro/--version/--codename + --arch dans --image-dir. "
"Si le fichier existe, il n'est PAS re-téléchargé.",
)
g_img = p.add_argument_group("Image")
g_img.add_argument(
"--distro",
default="ubuntu",
choices=DISTROS,
help="Distribution : ubuntu, debian ou fedora (défaut : ubuntu).",
)
g_img.add_argument(
"--version",
default=None,
help="Version de la distro (défaut : la version par défaut de la "
"distro). Voir --list-images pour la liste complète.",
)
g_img.add_argument(
"--codename",
help="Force le nom de code / la release (surcharge --version).",
)
g_img.add_argument(
"--arch",
default="amd64",
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
help="Architecture de l'image (défaut : amd64). amd64/x86_64, "
"arm64/aarch64 (Ubuntu/Debian/Fedora) ou s390x (Ubuntu). Toute arch "
"différente de l'hôte est ÉMULÉE (TCG, lente).",
)
g_img.add_argument(
"--image-dir",
type=Path,
default=DEFAULT_IMAGE_DIR,
help="Répertoire de cache des images cloud quand image_path est "
f"omis (défaut : {DEFAULT_IMAGE_DIR}).",
)
g_img.add_argument(
"--verify",
action="store_true",
help="Vérifie l'empreinte SHA256 après téléchargement (recommandé).",
)
g_vm = p.add_argument_group("VM")
g_vm.add_argument(
"--name",
help="Nom de la VM (virsh). Requis pour déployer ; inutile avec "
"--download-only.",
)
g_vm.add_argument(
"--hostname", help="Nom d'hôte interne (défaut : --name)."
)
g_vm.add_argument(
"--memory",
type=int,
default=None,
help="RAM en Mo (défaut : minimum requis par la version choisie, "
"voir --list-images).",
)
g_vm.add_argument(
"--vcpus", type=int, default=2, help="Nombre de vCPU (défaut : 2)."
)
g_vm.add_argument(
"--disk-size",
default=None,
help="Taille du disque virtuel, ex. 120G (défaut : minimum requis "
"par la version choisie, voir --list-images).",
)
g_vm.add_argument(
"--disk-dir",
type=Path,
default=Path("/var/lib/libvirt/images"),
help="Répertoire du qcow2 de travail (défaut : /var/lib/libvirt/images).",
)
g_vm.add_argument(
"--seed-dir",
type=Path,
default=Path("/var/lib/libvirt/images/iso"),
help="Répertoire du seed.iso (défaut : .../images/iso).",
)
g_vm.add_argument(
"--network",
default="network=default,model=virtio",
help="Argument --network de virt-install.",
)
g_vm.add_argument(
"--graphics",
default="none",
help="Argument --graphics (défaut : none).",
)
g_vm.add_argument(
"--osinfo", help="Force la valeur --osinfo (sinon déduite)."
)
g_vm.add_argument(
"--attach-console",
action="store_true",
help="Attache la console série (sinon --noautoconsole).",
)
g_vm.add_argument(
"--bios",
action="store_true",
help="Force l'amorçage BIOS hérité au lieu d'UEFI (par défaut UEFI ; "
"n'utiliser que si le firmware OVMF est absent).",
)
g_cloud = p.add_argument_group("cloud-init")
g_cloud.add_argument(
"--user",
default="erplibre",
help="Utilisateur créé (défaut : erplibre).",
)
g_cloud.add_argument(
"--password", help="Mot de passe en clair (déconseillé : visible)."
)
g_cloud.add_argument(
"--ask-password",
action="store_true",
help="Demande le mot de passe de façon interactive (sûr).",
)
g_cloud.add_argument(
"--password-hash",
help="Empreinte $6$... déjà calculée (openssl passwd -6).",
)
g_cloud.add_argument(
"--ssh-key",
action="append",
default=[],
metavar="FICHIER",
help="Fichier de clé publique SSH à injecter (répétable).",
)
g_cloud.add_argument(
"--locale",
default="fr_CA.UTF-8",
help="Locale (défaut : fr_CA.UTF-8).",
)
g_cloud.add_argument(
"--keyboard-layout",
default="ca",
help="Disposition clavier (défaut : ca).",
)
g_cloud.add_argument(
"--keyboard-variant",
default="multix",
help="Variante clavier (défaut : multix).",
)
g_cloud.add_argument(
"--package",
action="append",
default=[],
metavar="PKG",
help="Paquet APT additionnel (répétable).",
)
g_cloud.add_argument(
"--no-upgrade",
action="store_true",
help="N'exécute pas package_upgrade au premier boot.",
)
g_cloud.add_argument(
"--apt-update",
action="store_true",
help="Exécute « apt update » au 1er boot (package_update). Désactivé "
"par défaut : évite que cloud-init se bloque sur un miroir lent/"
"injoignable (SSH est déjà présent dans les images cloud).",
)
g_run = p.add_argument_group("Exécution")
g_run.add_argument(
"--no-sudo",
action="store_true",
help="N'utilise pas sudo pour les étapes privilégiées.",
)
g_run.add_argument(
"--force",
action="store_true",
help="Écrase le qcow2 de travail existant.",
)
g_run.add_argument(
"--no-wait-ip",
action="store_true",
help="N'attend pas l'attribution de l'IP à la fin.",
)
g_run.add_argument(
"--ip-timeout",
type=int,
default=90,
metavar="SEC",
help="Délai max d'attente de l'IP DHCP (défaut : 90 s).",
)
g_run.add_argument(
"--dry-run",
action="store_true",
help="Affiche les commandes et le user-data sans rien exécuter.",
)
g_run.add_argument(
"--download-only",
action="store_true",
help="Télécharge (et vérifie si --verify) l'image cloud puis quitte, "
"sans créer de VM. --name n'est pas requis.",
)
g_run.add_argument(
"-y",
"--assume-yes",
action="store_true",
help="Accepte automatiquement l'installation des dépendances manquantes.",
)
g_run.add_argument(
"--no-install-deps",
action="store_true",
help="N'installe jamais les dépendances manquantes (échoue si absentes).",
)
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
g_run.add_argument(
"--setup-host",
action="store_true",
help="Prépare l'hôte (paquets QEMU/libvirt, démon, groupe libvirt, "
"réseau default) puis quitte. Ne déploie aucune VM.",
)
g_run.add_argument(
"--list-images",
action="store_true",
help="Liste les distros/versions disponibles et leurs specs, "
"puis quitte.",
)
return p
def resolve_password(args: argparse.Namespace) -> str | None:
if args.password_hash:
return args.password_hash
if args.ask_password:
pw = getpass.getpass("Mot de passe pour l'utilisateur : ")
if pw != getpass.getpass("Confirmer : "):
sys.exit("Les mots de passe ne correspondent pas.")
return hash_password(pw)
if args.password:
return hash_password(args.password)
return None
def load_ssh_keys(paths: list[str]) -> list[str]:
keys: list[str] = []
for path in paths:
p = Path(path).expanduser()
if not p.exists():
sys.exit(f"Clé SSH introuvable : {p}")
keys.append(p.read_text().strip())
return keys
def main() -> None:
# Sortie ligne par ligne même quand stdout est un tube (menu todo) : sinon
# les en-têtes restent bufferisés et le déploiement paraît « gelé ».
try:
sys.stdout.reconfigure(line_buffering=True)
except (AttributeError, ValueError):
pass
args = build_parser().parse_args()
if args.list_images:
list_images()
return
[FIX] qemu: really prepare the host in the Deployment profile The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a one-liner ending in « || true » and hiding stderr, so a broken host looked installed. Reproduced on erplibre-arch-latest: every binary was present, yet virt-install failed and « virsh list --all » could not reach any hypervisor. Three distinct causes, all unhandled: - The user was never added to the libvirt group. Without it a non-root libvirt client falls back to qemu:///session, where the « default » network does not exist, so « --network network=default » fails while everything looks installed. Being in the group grants the RIGHT to reach qemu:///system but does NOT change the default URI, so virt-install and virsh now pass « --connect qemu:///system » explicitly (new LIBVIRT_URI constant). - dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the daemon was absent, and libvirt was already there. setup_host now forces them. - On a rolling distro, « make install_os » upgrades the kernel and the package manager removes /lib/modules/<running kernel>. modprobe bridge then fails and libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not installed »). No package fixes that, only a reboot: it is now diagnosed and reported instead of surfacing as an unreadable virsh error. The profile now calls « deploy_qemu.py --setup-host », which reuses the existing ensure_* chain, so package names stay defined in one place (TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman, zypper and brew. It fails loudly instead of « || true ». Verified on the Arch VM that failed: setup-host reports the stale kernel and exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the default network, and reports « Hôte prêt » (exit 0, idempotent on rerun). The generated command now reads « virt-install --connect qemu:///system ». Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-01 03:15:46 -04:00
# Préparation de l'hôte : aucune image, aucune distro cible, aucun --name.
# Traité AVANT la résolution de version/image pour rester utilisable seul.
if args.setup_host:
setup_host(
Runner(
use_sudo=not args.no_sudo and os.geteuid() != 0,
dry_run=args.dry_run,
),
args.assume_yes,
args.no_install_deps,
)
return
versions, default_version = DISTROS[args.distro]
if args.version is None:
args.version = default_version
if args.version not in versions:
sys.exit(
f"Version {args.version!r} inconnue pour {args.distro}. "
f"Choix : {', '.join(versions)} (voir --list-images)."
)
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
# Certaines architectures ne sont publiées que par une partie des distros
# (voir ARCH_DISTRO_SUPPORT) : on valide tôt plutôt qu'échouer au download.
supported = ARCH_DISTRO_SUPPORT.get(args.arch)
if supported is not None and args.distro not in supported:
sys.exit(
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
f"Architecture {args.arch} indisponible pour {args.distro!r} : "
f"images cloud publiées seulement pour {', '.join(supported)}."
)
code, default_osinfo, min_ram, min_disk = versions[args.version]
if args.codename:
code = args.codename
osinfo = args.osinfo or default_osinfo
# Dimensionnement par défaut = minimum requis par la version (libosinfo).
if args.memory is None:
args.memory = min_ram
if args.disk_size is None:
args.disk_size = min_disk
# Un nombre nu (« 30 ») serait pris pour des OCTETS par qemu-img et ferait
# échouer le resize : on suppose des Go par défaut (« 30 » -> « 30G »).
if re.fullmatch(r"\d+", str(args.disk_size)):
args.disk_size = f"{args.disk_size}G"
urls = image_candidates(
args.distro, code, args.arch, args.version, args.dry_run
)
url = urls[0]
# --verify s'appuie sur un SHA256SUMS style Ubuntu ; Debian/Fedora
# publient des sommes dans un autre format -> on saute proprement.
do_verify = args.verify and args.distro == "ubuntu"
if args.verify and not do_verify:
print(
f" Note : --verify n'est pris en charge que pour ubuntu "
f"(ignoré pour {args.distro})."
)
# Chemin de l'image : déduit automatiquement si non fourni.
if args.image_path is None:
args.image_path = args.image_dir / default_image_name(
args.distro, code, args.arch, args.version
)
runner = Runner(
use_sudo=not args.no_sudo and os.geteuid() != 0, dry_run=args.dry_run
)
# -- Mode téléchargement seul : aucun outil ni VM requis. --------------
if args.download_only:
print(
f"\n== Téléchargement image cloud "
f"({args.distro} {args.version} / {code}) =="
)
print(f" Destination : {args.image_path}")
download_image(urls, args.image_path, args.dry_run)
if do_verify:
verify_sha256(url, args.image_path, args.dry_run)
print("\nTerminé (téléchargement seul).")
return
# -- Déploiement complet -----------------------------------------------
if not args.name:
sys.exit(
"Erreur : --name est requis pour déployer une VM "
"(ou utilisez --download-only)."
)
args.hostname = args.hostname or args.name
pw_hash = resolve_password(args)
ssh_keys = load_ssh_keys(args.ssh_key)
if not pw_hash and not ssh_keys:
print(
"ATTENTION : ni mot de passe ni clé SSH -> connexion impossible à la VM.\n"
" Ajoutez --ssh-key, --ask-password ou --password-hash.\n",
file=sys.stderr,
)
disk = args.disk_dir / f"{args.name}.qcow2"
seed = args.seed_dir / f"{args.name}-seed.iso"
if not args.dry_run:
ensure_tools(runner, args.assume_yes, args.no_install_deps)
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
# Émulation requise si l'arch diffère de l'hôte : installe l'émulateur
# QEMU système adéquat (+ firmware UEFI pour arm64) et prévient de la
# lenteur.
if args.arch in EMULATOR_BINARY and args.arch != host_arch():
ensure_emulator(
args.arch, runner, args.assume_yes, args.no_install_deps
)
print(
[IMP] script qemu: support arm64/aarch64 (+ archi native marquée d'un *) Ajoute l'architecture arm64 (aarch64) au déploiement, en plus d'amd64 (x86_64) et s390x. Disponibilité réelle vérifiée (juillet 2026) : - arm64 : Ubuntu, Debian, Fedora (Arch : pas d'image cloud aarch64 -> rejet). - s390x : Ubuntu seulement (inchangé). deploy_qemu.py : - host_arch() : arch native de l'hôte ; toute arch différente est ÉMULÉE (TCG, --virt-type qemu) — plus de liste FOREIGN_ARCHES figée. - virt_install : arm64 -> --arch aarch64 --machine virt --boot uefi (firmware AAVMF résolu par libvirt) ; s390x inchangé ; x86 UEFI/OVMF inchangé. - ensure_emulator(arch) généralisé : installe qemu-system-aarch64 + firmware UEFI AAVMF (arm64) ou qemu-system-s390x (s390x), selon le gestionnaire de paquets, avec vérif de présence (binaire + firmware pour arm64). - Validation --arch tôt via ARCH_DISTRO_SUPPORT (message clair si la distro ne publie pas l'arch). Les URLs d'images arm64 marchent déjà via les alias (fedora/arch aarch64 ; ubuntu/debian arm64). todo.py : menus d'architecture (VM unique + infra) proposent amd64/arm64/ s390x selon la distro ; l'architecture NATIVE de l'hôte est marquée d'un * (défaut) ; les autres sont signalées « émulé, lent ». Le catalogue infra est restreint aux distros publiant l'arch choisie (arm64 -> ubuntu/debian/fedora, s390x -> ubuntu). install_debian_dependency.sh : wkhtmltopdf (deb amd64 en dur) ignoré best-effort sur toute arch non-x86_64 (au lieu d'avorter l'install). Validé : dry-runs Ubuntu/Debian/Fedora arm64 (bonnes URLs + virt-install aarch64/virt/uefi/qemu), rejet Arch arm64, non-régression s390x, menus et filtrage simulés (natif *, arm64 par distro). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 03:56:20 -04:00
f" Note : {args.arch} est ÉMULÉ (TCG) sur cet hôte "
f"({host_arch()}) — le boot et l'installation seront "
"nettement plus lents que l'architecture native."
)
print(f"\n== 1/5 Image cloud ({args.distro} {args.version} / {code}) ==")
download_image(urls, args.image_path, args.dry_run)
if do_verify:
verify_sha256(url, args.image_path, args.dry_run)
print(f"\n== 2-3/5 Disque de travail {disk} ({args.disk_size}) ==")
prepare_disk(args.image_path, disk, args.disk_size, runner, args.force)
print(f"\n== 4/5 Seed cloud-init {seed} ==")
cloud_cfg = build_cloud_config(args, pw_hash, ssh_keys)
build_seed(cloud_cfg, args.hostname, seed, runner)
resolved_osinfo = osinfo_arg(osinfo, args.distro)
print(f"\n== 5/5 virt-install (--osinfo {resolved_osinfo}) ==")
ensure_network(network_name(args.network), runner)
virt_install(args, disk, seed, resolved_osinfo, runner)
has_key = bool(ssh_keys)
print("\nTerminé. Suivi :")
print(" virsh list --all")
print(f" virsh console {args.name} # Ctrl+] pour quitter")
if args.dry_run:
print("\n Connexion SSH (IP attribuée au 1er boot) :")
print(f" {ssh_command(args.user, '<IP>', has_key)}")
return
if args.no_wait_ip:
print(
f"\n Récupérer l'IP : virsh domifaddr {args.name} --source lease"
)
return
print(f"\n Attente de l'IP (bail DHCP, max {args.ip_timeout} s)…")
ip = wait_for_ip(args.name, runner.use_sudo, args.ip_timeout)
if ip:
print(f" IP : {ip}")
print(" Connexion SSH :")
print(f" {ssh_command(args.user, ip, has_key)}")
else:
print(" IP non obtenue dans le délai (cloud-init encore en cours ?).")
print(f" Réessayez : virsh domifaddr {args.name} --source lease")
if __name__ == "__main__":
main()