[FIX] qemu: reboot after the install upgrades the kernel, and fix virtinst cache

On a rolling distro every fresh development VM was born unable to create VMs.
« make install_os » runs a full system upgrade, the kernel package is replaced
and /lib/modules/<running kernel> disappears. modprobe bridge then fails and
libvirt cannot create virbr0, so the « default » network stays inactive and
virt-install dies on « network 'default' is not active » -- with every package
correctly installed. Measured twice on a freshly created Arch VM: booted on
7.1.3-arch1-3 at 05:44, upgraded to 7.1.5.arch1-2 at 05:46.

Only a reboot fixes it, and one is enough: the default network is already
flagged autostart, so libvirt brings it up by itself once the modules match.
--setup-host therefore gains --reboot-if-needed, used by the deployment
profile. The reboot is scheduled through systemd-run --on-active=5 rather than
issued immediately, otherwise it would kill the installer's SSH session and the
orchestrator would report a failure for a VM that actually succeeded. Without
the flag the behaviour is unchanged: explain and exit 1, which is what a
workstation wants.

Also fix « Error setting up logfile: No write access to
/var/tmp/erplibre-virtinst/virt-manager »: that path was shared, so a first run
under sudo created it as root and later non-root runs could not write. It is
now per-UID.

Verified on the Arch VM that failed: without the flag it exits 1 with the
diagnosis; with it, the reboot is scheduled and the command still returns 0;
after the reboot the kernel and modules match, « default » is active on its own
and virbr0 exists. The cache directory is created as erplibre:erplibre 0700.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mathieu Benoit 2026-08-01 04:00:21 -04:00
parent fba3b8aed1
commit 74ee584ffa
2 changed files with 60 additions and 4 deletions

View file

@ -689,7 +689,30 @@ def kernel_modules_stale() -> str:
)
def setup_host(runner: Runner, assume_yes: bool, no_install: bool) -> None:
def schedule_reboot(runner: Runner) -> None:
"""Programme un redémarrage DIFFÉRÉ et détaché de la session courante.
Un « systemctl reboot » immédiat tuerait le SSH de l'installation, et
l'orchestrateur compterait la VM en échec alors que tout s'est bien passé.
On laisse donc quelques secondes pour que la commande distante rende la
main proprement.
"""
if shutil.which("systemd-run"):
runner.run(
["systemd-run", "--on-active=5", "systemctl", "reboot"],
privileged=True,
check=False,
)
return
runner.run(["shutdown", "-r", "+1"], privileged=True, check=False)
def setup_host(
runner: Runner,
assume_yes: bool,
no_install: bool,
reboot_if_needed: bool = False,
) -> 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 ».
@ -725,6 +748,21 @@ def setup_host(runner: Runner, assume_yes: bool, no_install: bool) -> None:
f"{'actif' if active else 'INACTIF'}"
f" / {'autostart' if autostart else 'PAS autostart'}"
)
if not active and stale:
# Le réseau est déjà « autostart » : après le redémarrage, libvirt le
# monte tout seul avec les modules du nouveau noyau. Un seul reboot
# suffit donc à rendre l'hôte utilisable, sans repasser par ici.
if reboot_if_needed:
print(
"\n↻ Redémarrage programmé (dans quelques secondes) : c'est la"
" SEULE façon de retrouver les modules du noyau.\n"
" Au retour, le réseau « default » démarrera seul"
" (autostart déjà actif)."
)
schedule_reboot(runner)
return
sys.exit(f"Erreur : l'hôte n'est pas prêt.\n {stale}")
if not (ok and active):
sys.exit(
"Erreur : l'hôte n'est pas prêt.\n"
@ -1428,10 +1466,18 @@ def virt_install(
# 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.
# Chemin propre à l'UID : un répertoire partagé finit créé par root lors
# d'un premier passage sous sudo, puis devient illisible pour l'utilisateur
# (« Error setting up logfile: No write access to … /virt-manager »).
cache_dir = f"/var/tmp/erplibre-virtinst-{os.getuid()}"
try:
os.makedirs(cache_dir, mode=0o700, exist_ok=True)
except OSError:
pass
log_env = [
"env",
"XDG_CACHE_HOME=/var/tmp/erplibre-virtinst",
"HOME=/var/tmp/erplibre-virtinst",
f"XDG_CACHE_HOME={cache_dir}",
f"HOME={cache_dir}",
]
runner.run(log_env + cmd, privileged=True)
@ -1717,6 +1763,12 @@ def build_parser() -> argparse.ArgumentParser:
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(
"--reboot-if-needed",
action="store_true",
help="Avec --setup-host : redémarre si le noyau a été mis à jour "
"depuis le démarrage (sinon libvirt ne peut pas créer virbr0).",
)
g_run.add_argument(
"--list-images",
action="store_true",
@ -1773,6 +1825,7 @@ def main() -> None:
),
args.assume_yes,
args.no_install_deps,
args.reboot_if_needed,
)
return

View file

@ -3078,7 +3078,10 @@ class TODO:
# Sans le groupe, virt-install retombe sur qemu:///session où « default »
# n'existe pas : la VM échoue alors que tous les paquets sont installés.
# L'ancien one-liner finissait par « || true » et masquait ses erreurs.
_QEMU_QEMU_PKGS = "./script/qemu/deploy_qemu.py --setup-host --assume-yes"
_QEMU_QEMU_PKGS = (
"./script/qemu/deploy_qemu.py --setup-host --assume-yes"
" --reboot-if-needed"
)
def _qemu_ask_prod(self):
"""Environnement cible : dev (défaut) ou prod. En PROD : ERPLibre est