From 49bc354d25b6af12361ee85d365414a6bf0c9ab8 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Thu, 20 Aug 2026 02:56:21 -0400 Subject: [PATCH] =?UTF-8?q?[FIX]=20script=20todo:=20d=C3=A9marrer=20le=20b?= =?UTF-8?q?ureau,=20pas=20seulement=20l'activer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Une VM graphique restait sur une console texte jusqu'au premier redémarrage. GNOME installé, gdm3 installé, graphical.target par défaut, lien display-manager.service posé par le paquet — et rien à l'écran. Deux causes superposées, mesurées sur erplibre-ubuntu-2604-gnome : graphical.target était DÉJÀ atteinte quand le paquet est arrivé, et une cible active ne rattrape pas un service ajouté après coup. Et « systemctl enable gdm » rend 0 sans rien faire sur Debian et Ubuntu : l'unité n'a pas de « WantedBy », seulement l'alias que le paquet pose lui-même. Le bureau est donc démarré, avec repli sur le service de la saveur, et l'échec se dit au lieu de se taire. Vérifié : bureau arrêté puis fragment rejoué -> gnome-shell revient, écran de connexion GDM à l'image. --- EN --- A graphical VM stayed on a text console until its first reboot. GNOME installed, gdm3 installed, graphical.target the default, the display-manager.service alias in place by the package — and nothing on screen. Two causes stacked, measured on erplibre-ubuntu-2604-gnome: graphical.target had ALREADY been reached when the package arrived, and an active target does not pick up a service added afterwards. And "systemctl enable gdm" returns 0 doing nothing on Debian and Ubuntu: the unit has no "WantedBy", only the alias the package installs itself. The desktop is therefore started, with a fallback to the flavour's service, and a failure says so instead of staying quiet. Verified: desktop stopped then the fragment replayed -> gnome-shell back, GDM greeter on screen. Assisted-by: Claude Opus 5 --- script/todo/todo.py | 19 +++++++ script/todo/todo_i18n.py | 8 +++ test/test_qemu_desktop_tools.py | 93 +++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) diff --git a/script/todo/todo.py b/script/todo/todo.py index 76143b0..0f6540b 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -5333,6 +5333,25 @@ class TODO: # images cloud démarrent en multi-user.target. "sudo systemctl set-default graphical.target || true; " f"sudo systemctl enable {de['service']} >/dev/null 2>&1 || true; " + # Et il faut le DÉMARRER, pas seulement l'activer. Deux raisons, + # toutes deux mesurées sur erplibre-ubuntu-2604-gnome : + # + # - graphical.target était DÉJÀ atteinte quand le paquet est + # arrivé, et une cible active ne rattrape pas un service ajouté + # après coup : display-manager.service est resté inactif ; + # - sur Debian et Ubuntu, « systemctl enable gdm » rend 0 sans + # rien faire — l'unité n'a pas de « WantedBy », seulement + # « Alias=display-manager.service » que le paquet a déjà posé. + # + # Résultat : GNOME installé, gdm3 installé, cible graphique par + # défaut… et la console de la VM restait en mode texte jusqu'au + # premier redémarrage. L'écran, c'est justement ce qu'on est venu + # chercher sur une VM graphique. + "if sudo systemctl start display-manager.service 2>/dev/null || " + f"sudo systemctl start {de['service']} 2>/dev/null; then " + f'echo " {t("graphical session started")}"; ' + f'else echo " ⚠ {t("graphical session not started; reboot the VM")}"; ' + "fi; " # xrdp là où il existe ; sur Arch c'est TigerVNC, qui se configure # par utilisateur et n'a pas de service à activer d'office. "if command -v xrdp >/dev/null 2>&1; then " diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index e6f6e21..eb58a00 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -2244,6 +2244,14 @@ TRANSLATIONS = { "fr": "À lancer sur VOTRE poste :", "en": "Run this on YOUR workstation:", }, + "graphical session started": { + "fr": "session graphique démarrée", + "en": "graphical session started", + }, + "graphical session not started; reboot the VM": { + "fr": "session graphique non démarrée ; redémarrer la VM", + "en": "graphical session not started; reboot the VM", + }, "Hypervisor console (QEMU screen, no guest server)": { "fr": "Console de l'hyperviseur (écran QEMU, rien dans l'invitée)", "en": "Hypervisor console (QEMU screen, no guest server)", diff --git a/test/test_qemu_desktop_tools.py b/test/test_qemu_desktop_tools.py index 293355d..a71a0be 100644 --- a/test/test_qemu_desktop_tools.py +++ b/test/test_qemu_desktop_tools.py @@ -631,6 +631,99 @@ class TestPycharmCommunity(unittest.TestCase): self.assertIn("JetBrains", self.cmd) +class TestTheDesktopActuallyStarts(unittest.TestCase): + """Installer un bureau ne suffit pas : il faut le DÉMARRER. + + Vécu sur erplibre-ubuntu-2604-gnome, et le diagnostic ne sautait pas aux + yeux : GNOME installé, gdm3 installé, graphical.target par défaut, lien + display-manager.service en place — et la console de la VM restait en mode + texte. Deux causes superposées : + + - graphical.target était DÉJÀ atteinte quand le paquet est arrivé, et une + cible active ne rattrape pas un service ajouté après coup ; + - « systemctl enable gdm » rend 0 sans rien faire sur Debian et Ubuntu : + l'unité n'a pas de « WantedBy », seulement un alias que le paquet pose. + """ + + def setUp(self): + self.todo = TODO.__new__(TODO) + self.cmd = self.todo._qemu_desktop_remote_cmd("gnome", "deb") + + def _start_block(self, cmd=None): + """Le seul « if » qui démarre le bureau, extrait tel quel.""" + cmd = cmd or self.cmd + start = cmd.index("if sudo systemctl start display-manager") + return cmd[start : cmd.index("fi; ", start) + 4] + + def test_it_starts_and_does_not_only_enable(self): + self.assertIn("systemctl start display-manager.service", self.cmd) + + def test_it_falls_back_to_the_desktop_service(self): + """« display-manager.service » est un alias que les paquets Debian + posent ; ailleurs c'est « gdm » qui porte le WantedBy.""" + block = self._start_block() + self.assertIn("systemctl start gdm", block) + + def test_it_comes_after_the_default_target_and_before_xrdp(self): + self.assertLess( + self.cmd.index("set-default graphical.target"), + self.cmd.index("start display-manager.service"), + ) + self.assertLess( + self.cmd.index("start display-manager.service"), + self.cmd.index("command -v xrdp"), + ) + + def _run(self, systemctl_body): + import os + import tempfile + + with tempfile.TemporaryDirectory() as tmp: + bin_dir = pathlib.Path(tmp) / "bin" + bin_dir.mkdir() + (bin_dir / "sudo").write_text('#!/bin/bash\nexec "$@"\n') + (bin_dir / "systemctl").write_text( + f"#!/bin/bash\n{systemctl_body}\n" + ) + for n in ("sudo", "systemctl"): + (bin_dir / n).chmod(0o755) + res = subprocess.run( + ["bash", "-c", self._start_block()], + capture_output=True, + text=True, + env=dict(os.environ, PATH=f"{bin_dir}:/usr/bin:/bin"), + timeout=30, + ) + return res.stdout + + def test_the_alias_path_reports_a_started_session(self): + out = self._run("exit 0") + self.assertIn("session", out.lower()) + self.assertNotIn("⚠", out) + + def test_the_fallback_path_also_reports_started(self): + """display-manager absent, gdm présent : c'est le cas d'Arch.""" + out = self._run( + 'case "$*" in *display-manager*) exit 1;; *) exit 0;; esac' + ) + self.assertNotIn("⚠", out) + + def test_when_nothing_starts_it_says_to_reboot(self): + """Le pire serait de se taire : l'utilisateur cherche un écran.""" + out = self._run("exit 1") + self.assertIn("⚠", out) + self.assertIn("boot", out.lower() + "reboot") + + def test_the_block_is_valid_shell(self): + res = subprocess.run( + ["bash", "-n"], + input=self._start_block(), + capture_output=True, + text=True, + ) + self.assertEqual(0, res.returncode, res.stderr) + + class TestPycharmFirstOpen(unittest.TestCase): """Ouverture sans écran, pour que le .idea existe avant l'installation.