[FIX] script todo: démarrer le bureau, pas seulement l'activer

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
This commit is contained in:
Mathieu Benoit 2026-08-20 02:56:21 -04:00
parent 0bd7c2fb1d
commit 49bc354d25
3 changed files with 120 additions and 0 deletions

View file

@ -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 "

View file

@ -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)",

View file

@ -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.