[FIX] déploiement qemu : amorcer Fedora en BIOS, sa chaîne UEFI se fige

Aucune VM Fedora ne démarrait : pas de console, pas de bail DHCP, une machine
« en cours d'exécution » qui ne fait rien. Le micrologiciel charge et DÉMARRE
le chargeur — il l'annonce — puis se fige sans écrire un octet sur le disque.
La même image en BIOS démarre son noyau : ce n'est donc ni l'image, saine, ni
sa partition EFI, dont le chemin de repli est bien là. Ni l'entropie ni la
machine q35 n'y changent rien. Une table nomme les distributions concernées et
« --bios » l'emporte toujours, pour l'hôte qui n'a pas OVMF.

--- EN ---

No Fedora VM would start: no console, no DHCP lease, a machine "running" that
does nothing. The firmware loads and STARTS the loader — it says so — then
freezes without writing a byte to disk. The same image under BIOS boots its
kernel: so it is neither the image, which is sound, nor its EFI partition,
whose fallback path is present. Neither entropy nor the q35 machine changes
anything. A table names the distributions concerned, and "--bios" always
wins, for the host that has no OVMF.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-09-14 14:41:19 -04:00
parent 2892606690
commit 814251f19f
2 changed files with 61 additions and 5 deletions

View file

@ -1776,6 +1776,25 @@ def hostname_valide(nom: str) -> str:
TZ_ALIASES = "/usr/share/zoneinfo/tzdata.zi"
# Distributions dont la chaîne UEFI ne démarre pas sur les OVMF courants.
#
# L'image de Fedora charge et DÉMARRE son chargeur — le micrologiciel l'annonce
# — puis se fige sans écrire un octet sur le disque. La même image en BIOS
# démarre son noyau normalement : ce n'est donc ni l'image, ni la partition
# EFI, dont le chemin de repli est bien là. Ni l'entropie ni la machine q35 n'y
# changent rien.
#
# Le symptôme visible depuis le déploiement est muet : aucune console, aucun
# bail DHCP, une VM « en cours d'exécution » qui ne fait rien. D'où cette table
# plutôt qu'un diagnostic à refaire.
BIOS_OBLIGATOIRE = {"fedora"}
def amorcage_bios(distro: str, demande: bool) -> bool:
"""Faut-il amorcer en BIOS ? La demande explicite l'emporte toujours."""
return bool(demande) or distro in BIOS_OBLIGATOIRE
def canonical_timezone(tz: str, table: str = TZ_ALIASES) -> str:
"""Le nom canonique d'un fuseau, quand le système sait le dire.
@ -4018,11 +4037,10 @@ def virt_install(
elif not args.bios:
# 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.
# boucle « Booting... » en SeaBIOS. --bios force l'ancien BIOS, que
# certaines distributions exigent — voir BIOS_OBLIGATOIRE.
# 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,"
@ -4338,8 +4356,9 @@ def build_parser() -> argparse.ArgumentParser:
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).",
help="Force l'amorçage BIOS hérité au lieu d'UEFI. UEFI est le "
"défaut, sauf pour les distributions dont la chaîne UEFI ne démarre "
"pas (Fedora), où le BIOS est retenu d'office.",
)
g_cloud = p.add_argument_group("cloud-init")
@ -4686,6 +4705,7 @@ def main() -> None:
# rien d'autre qu'un avertissement de cloud-init ne le dise. Le nom de
# DOMAINE, lui, peut le porter — les deux ne se ressemblent qu'en général.
args.hostname = args.hostname or hostname_valide(args.name)
args.bios = amorcage_bios(args.distro, args.bios)
pw_hash = resolve_password(args)
ssh_keys = load_ssh_keys(args.ssh_key)

View file

@ -28,6 +28,7 @@ RACINE = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(RACINE))
from script.qemu.deploy_qemu import ( # noqa: E402
amorcage_bios,
canonical_timezone,
hostname_valide,
)
@ -110,5 +111,40 @@ class TestFuseau(unittest.TestCase):
)
class TestAmorcage(unittest.TestCase):
"""Quelle image démarre en UEFI, laquelle exige le BIOS.
L'échec est MUET : le micrologiciel charge et démarre le chargeur, puis se
fige sans écrire un octet. Vu du déploiement, il ne reste qu'une VM « en
cours d'exécution » sans console et sans bail DHCP, et la cause est à
chercher pendant une heure. D'où une table plutôt qu'un diagnostic à
refaire.
"""
def test_une_distribution_qui_lexige_part_en_bios(self):
self.assertTrue(amorcage_bios("fedora", False))
def test_les_autres_gardent_uefi(self):
"""UEFI reste le défaut : les images cloud récentes n'embarquent plus
le chargeur BIOS et partent en boucle sous SeaBIOS."""
for d in (
"debian",
"ubuntu",
"arch",
"rocky",
"almalinux",
"opensuse",
):
self.assertFalse(amorcage_bios(d, False), d)
def test_la_demande_explicite_lemporte(self):
"""« --bios » sert aussi quand OVMF manque de la machine hôte."""
for d in ("debian", "fedora", "inconnue"):
self.assertTrue(amorcage_bios(d, True), d)
def test_une_distribution_inconnue_garde_le_defaut(self):
self.assertFalse(amorcage_bios("inconnue", False))
if __name__ == "__main__":
unittest.main()