[ADD] qemu menu : récupérer des fichiers dans le disque d'une VM
Une VM qui ne démarre plus garde ses fichiers : libguestfs monte son
qcow2 sans elle. Toute commande porte « --ro », et c'est ce qui change la
manœuvre : ouvrir en écriture le disque d'une machine allumée corrompt
son système de fichiers, la lire ne risque rien. Un arrêt reste proposé
et non imposé — une lecture vivante voit un état peut-être incohérent,
un fichier à moitié écrit, un journal non rejoué. Le nom du paquet suit
la distribution, celui de Debian n'existant nulle part ailleurs.
Vérifié : 16 tests, rougis par quatre mutations — perdre le « --ro »,
donner le nom Debian partout, dupliquer un numéro, retirer l'entrée.
--- EN ---
A VM that no longer boots still holds its files: libguestfs mounts its
qcow2 without it. Every command carries « --ro », and that is what
changes the operation: opening a running machine's disk for writing
corrupts its filesystem, reading it risks nothing. A shutdown stays
offered, not imposed — a live read sees a possibly torn state, a
half-written file, an unreplayed journal. The package name follows the
distribution, Debian's existing nowhere else.
Checked: 16 tests, turned red by four mutations — losing « --ro », using
Debian's name everywhere, duplicating a number, dropping the entry.
Assisted-by: Claude Opus 5
2026-09-03 01:23:34 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
"""Menu QEMU/KVM : récupérer des fichiers dans le disque d'une VM.
|
|
|
|
|
|
|
|
|
|
Une VM qui ne démarre plus garde ses fichiers : le qcow2 est là, et libguestfs
|
|
|
|
|
sait le monter SANS la machine. Le chemin complet : poser les outils selon la
|
|
|
|
|
distribution de l'hôte, lister les systèmes de fichiers du disque, en choisir
|
|
|
|
|
un, parcourir un répertoire, puis extraire vers l'hôte.
|
|
|
|
|
|
|
|
|
|
Frontière claire : ici on LIT un disque, on n'y écrit jamais. Toute commande
|
|
|
|
|
porte « --ro » — c'est ce qui rend l'opération sûre même sur une VM allumée,
|
|
|
|
|
et c'est aussi pourquoi rien de ce fichier ne peut abîmer une machine.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import shlex
|
|
|
|
|
import shutil
|
|
|
|
|
import subprocess
|
|
|
|
|
|
[ADD] qemu diagnostic : un relevé à transmettre, Gérer scindé en trois
Un problème de VM se résout souvent par quelqu'un qui n'a pas accès à la
machine : vingt et une sondes en lecture écrivent un relevé unique —
hôte, hyperviseur, GPU, outils, stockage — chacune bornée dans le temps,
une commande qui pend ne devant pas retenir le rapport. Sur le pilote
NVIDIA propriétaire, trois conditions vivent hors des briques Mesa et
QEMU, dont la liste de périphériques où libvirt n'ajoute jamais les nœuds
de la carte : le relevé les nomme et propose la commande qui installe ce
qui lui manque, affichée avant la question. Gérer se scinde en trois.
--- EN ---
A VM problem is often solved by someone with no access to the machine:
twenty-one read-only probes write one report — host, hypervisor, GPU,
tools, storage — each time-bounded, since a command that hangs must not
hold the report. On the proprietary NVIDIA driver three conditions live
outside the Mesa and QEMU pieces, among them the device list where
libvirt never adds the card's own nodes: the report names them and offers
the command installing what it lacks, shown before the question is put.
The Manage menu splits in three.
Assisted-by: Claude Opus 5
2026-09-03 04:49:23 -04:00
|
|
|
from script.todo.qemu_privilege import (
|
|
|
|
|
install_cmd_for,
|
|
|
|
|
system_env,
|
|
|
|
|
system_path,
|
|
|
|
|
)
|
[ADD] qemu menu : récupérer des fichiers dans le disque d'une VM
Une VM qui ne démarre plus garde ses fichiers : libguestfs monte son
qcow2 sans elle. Toute commande porte « --ro », et c'est ce qui change la
manœuvre : ouvrir en écriture le disque d'une machine allumée corrompt
son système de fichiers, la lire ne risque rien. Un arrêt reste proposé
et non imposé — une lecture vivante voit un état peut-être incohérent,
un fichier à moitié écrit, un journal non rejoué. Le nom du paquet suit
la distribution, celui de Debian n'existant nulle part ailleurs.
Vérifié : 16 tests, rougis par quatre mutations — perdre le « --ro »,
donner le nom Debian partout, dupliquer un numéro, retirer l'entrée.
--- EN ---
A VM that no longer boots still holds its files: libguestfs mounts its
qcow2 without it. Every command carries « --ro », and that is what
changes the operation: opening a running machine's disk for writing
corrupts its filesystem, reading it risks nothing. A shutdown stays
offered, not imposed — a live read sees a possibly torn state, a
half-written file, an unreplayed journal. The package name follows the
distribution, Debian's existing nowhere else.
Checked: 16 tests, turned red by four mutations — losing « --ro », using
Debian's name everywhere, duplicating a number, dropping the entry.
Assisted-by: Claude Opus 5
2026-09-03 01:23:34 -04:00
|
|
|
from script.todo.todo_i18n import t
|
|
|
|
|
|
|
|
|
|
# Le paquet qui apporte guestfish, par gestionnaire de paquets. Il ne vit pas
|
|
|
|
|
# dans la table du déploiement : ces outils ne servent QU'À la récupération,
|
|
|
|
|
# et les poser sur chaque hôte qui déploie une VM serait du poids pour rien.
|
|
|
|
|
#
|
|
|
|
|
# Les noms diffèrent d'une distribution à l'autre et le nom Debian ne marche
|
|
|
|
|
# nulle part ailleurs : « libguestfs-tools » sur apt, « guestfs-tools » sur
|
|
|
|
|
# dnf et zypper depuis que le paquet a été scindé, « libguestfs » sur pacman.
|
|
|
|
|
GUESTFS_PACKAGES = {
|
|
|
|
|
"apt": "libguestfs-tools",
|
|
|
|
|
"dnf": "guestfs-tools",
|
|
|
|
|
"pacman": "libguestfs",
|
|
|
|
|
"zypper": "guestfs-tools",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# guestfish suffit à tout faire ; les deux autres ne font que présenter mieux
|
|
|
|
|
# ce qu'il montre déjà. Les distinguer évite d'exiger un paquet complet là où
|
|
|
|
|
# l'essentiel est présent.
|
|
|
|
|
GUESTFS_BIN_ESSENTIEL = "guestfish"
|
|
|
|
|
GUESTFS_BIN_CONFORT = ("virt-filesystems", "virt-df")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def guestfs_install_cmd():
|
|
|
|
|
"""(commande d'installation, paquet) pour cet hôte, ou (None, None).
|
|
|
|
|
|
[ADD] qemu diagnostic : un relevé à transmettre, Gérer scindé en trois
Un problème de VM se résout souvent par quelqu'un qui n'a pas accès à la
machine : vingt et une sondes en lecture écrivent un relevé unique —
hôte, hyperviseur, GPU, outils, stockage — chacune bornée dans le temps,
une commande qui pend ne devant pas retenir le rapport. Sur le pilote
NVIDIA propriétaire, trois conditions vivent hors des briques Mesa et
QEMU, dont la liste de périphériques où libvirt n'ajoute jamais les nœuds
de la carte : le relevé les nomme et propose la commande qui installe ce
qui lui manque, affichée avant la question. Gérer se scinde en trois.
--- EN ---
A VM problem is often solved by someone with no access to the machine:
twenty-one read-only probes write one report — host, hypervisor, GPU,
tools, storage — each time-bounded, since a command that hangs must not
hold the report. On the proprietary NVIDIA driver three conditions live
outside the Mesa and QEMU pieces, among them the device list where
libvirt never adds the card's own nodes: the report names them and offers
the command installing what it lacks, shown before the question is put.
The Manage menu splits in three.
Assisted-by: Claude Opus 5
2026-09-03 04:49:23 -04:00
|
|
|
macOS y rend None comme tout hôte au gestionnaire inconnu, et c'est juste :
|
|
|
|
|
libguestfs n'y tourne pas.
|
[ADD] qemu menu : récupérer des fichiers dans le disque d'une VM
Une VM qui ne démarre plus garde ses fichiers : libguestfs monte son
qcow2 sans elle. Toute commande porte « --ro », et c'est ce qui change la
manœuvre : ouvrir en écriture le disque d'une machine allumée corrompt
son système de fichiers, la lire ne risque rien. Un arrêt reste proposé
et non imposé — une lecture vivante voit un état peut-être incohérent,
un fichier à moitié écrit, un journal non rejoué. Le nom du paquet suit
la distribution, celui de Debian n'existant nulle part ailleurs.
Vérifié : 16 tests, rougis par quatre mutations — perdre le « --ro »,
donner le nom Debian partout, dupliquer un numéro, retirer l'entrée.
--- EN ---
A VM that no longer boots still holds its files: libguestfs mounts its
qcow2 without it. Every command carries « --ro », and that is what
changes the operation: opening a running machine's disk for writing
corrupts its filesystem, reading it risks nothing. A shutdown stays
offered, not imposed — a live read sees a possibly torn state, a
half-written file, an unreplayed journal. The package name follows the
distribution, Debian's existing nowhere else.
Checked: 16 tests, turned red by four mutations — losing « --ro », using
Debian's name everywhere, duplicating a number, dropping the entry.
Assisted-by: Claude Opus 5
2026-09-03 01:23:34 -04:00
|
|
|
"""
|
[ADD] qemu diagnostic : un relevé à transmettre, Gérer scindé en trois
Un problème de VM se résout souvent par quelqu'un qui n'a pas accès à la
machine : vingt et une sondes en lecture écrivent un relevé unique —
hôte, hyperviseur, GPU, outils, stockage — chacune bornée dans le temps,
une commande qui pend ne devant pas retenir le rapport. Sur le pilote
NVIDIA propriétaire, trois conditions vivent hors des briques Mesa et
QEMU, dont la liste de périphériques où libvirt n'ajoute jamais les nœuds
de la carte : le relevé les nomme et propose la commande qui installe ce
qui lui manque, affichée avant la question. Gérer se scinde en trois.
--- EN ---
A VM problem is often solved by someone with no access to the machine:
twenty-one read-only probes write one report — host, hypervisor, GPU,
tools, storage — each time-bounded, since a command that hangs must not
hold the report. On the proprietary NVIDIA driver three conditions live
outside the Mesa and QEMU pieces, among them the device list where
libvirt never adds the card's own nodes: the report names them and offers
the command installing what it lacks, shown before the question is put.
The Manage menu splits in three.
Assisted-by: Claude Opus 5
2026-09-03 04:49:23 -04:00
|
|
|
return install_cmd_for(GUESTFS_PACKAGES)
|
[ADD] qemu menu : récupérer des fichiers dans le disque d'une VM
Une VM qui ne démarre plus garde ses fichiers : libguestfs monte son
qcow2 sans elle. Toute commande porte « --ro », et c'est ce qui change la
manœuvre : ouvrir en écriture le disque d'une machine allumée corrompt
son système de fichiers, la lire ne risque rien. Un arrêt reste proposé
et non imposé — une lecture vivante voit un état peut-être incohérent,
un fichier à moitié écrit, un journal non rejoué. Le nom du paquet suit
la distribution, celui de Debian n'existant nulle part ailleurs.
Vérifié : 16 tests, rougis par quatre mutations — perdre le « --ro »,
donner le nom Debian partout, dupliquer un numéro, retirer l'entrée.
--- EN ---
A VM that no longer boots still holds its files: libguestfs mounts its
qcow2 without it. Every command carries « --ro », and that is what
changes the operation: opening a running machine's disk for writing
corrupts its filesystem, reading it risks nothing. A shutdown stays
offered, not imposed — a live read sees a possibly torn state, a
half-written file, an unreplayed journal. The package name follows the
distribution, Debian's existing nowhere else.
Checked: 16 tests, turned red by four mutations — losing « --ro », using
Debian's name everywhere, duplicating a number, dropping the entry.
Assisted-by: Claude Opus 5
2026-09-03 01:23:34 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class QemuRecoverMixin:
|
|
|
|
|
"""Menu QEMU/KVM : récupérer des fichiers dans le disque d'une VM.
|
|
|
|
|
|
|
|
|
|
Une VM qui ne démarre plus garde ses fichiers : le qcow2 est là, et
|
|
|
|
|
libguestfs sait le monter SANS la machine. Le chemin complet : poser les
|
|
|
|
|
outils selon la distribution de l'hôte, lister les systèmes de fichiers du
|
|
|
|
|
disque, en choisir un, parcourir un répertoire, puis extraire vers l'hôte.
|
|
|
|
|
|
|
|
|
|
Frontière claire : ici on LIT un disque, on n'y écrit jamais.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def _qemu_guestfish_cmd(self, disk, *commandes):
|
|
|
|
|
"""Commande guestfish en LECTURE SEULE sur ce disque.
|
|
|
|
|
|
|
|
|
|
« --ro » n'est pas une précaution parmi d'autres : sans lui, ouvrir le
|
|
|
|
|
disque d'une VM allumée corrompt son système de fichiers. Avec lui, la
|
|
|
|
|
lecture est sûre à tout moment — au pire elle voit un instantané
|
|
|
|
|
incohérent, ce que le menu annonce.
|
|
|
|
|
"""
|
|
|
|
|
parties = " : ".join(commandes)
|
|
|
|
|
return (
|
|
|
|
|
f"guestfish --ro -a {shlex.quote(str(disk))}"
|
|
|
|
|
f"{' ' + parties if parties else ''}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _qemu_guestfish_lines(self, disk, *commandes, timeout=180):
|
|
|
|
|
"""Lignes rendues par guestfish, ou [] s'il échoue.
|
|
|
|
|
|
|
|
|
|
L'appliance libguestfs démarre un noyau : compter en secondes, pas en
|
|
|
|
|
dixièmes. Un timeout généreux vaut mieux qu'un échec sur une machine
|
|
|
|
|
chargée.
|
|
|
|
|
"""
|
|
|
|
|
cmd = self._qemu_guestfish_cmd(disk, *commandes)
|
|
|
|
|
try:
|
|
|
|
|
res = subprocess.run(
|
|
|
|
|
cmd,
|
|
|
|
|
shell=True,
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
timeout=timeout,
|
[FIX] qemu menu : sortir le venv du PATH des outils système
Le « bin » du venv est en tête du PATH de chaque commande lancée par le
menu, et il contient un python3. Un outil système écrit en Python et
amorcé par « env python3 » s'y amorce donc, dans un interpréteur où les
modules de la distribution n'existent pas : l'import échoue sur un module
que la machine possède pourtant. Sous sudo le piège était invisible, sudo
réinitialisant le PATH ; le retirer là où il ne servait plus l'a mis au
jour.
Vérifié : 6 tests, rougis par trois mutations. La ligne d'amorçage des
outils visés n'a pas été inspectée : le mécanisme est démontré, pas qu'il
soit la cause sur un hôte donné.
--- EN ---
The venv's « bin » leads the PATH of every command the menu launches, and
it holds a python3. A system tool written in Python and started through
« env python3 » therefore boots on that interpreter, where the
distribution's modules do not exist: the import fails on a module the
machine does have. Under sudo the trap was invisible, sudo resetting the
PATH; removing it where it was no longer needed brought it out.
Checked: 6 tests, turned red by three mutations. The shebang of the tools
concerned was not inspected: the mechanism is demonstrated, not that it
is the cause on any given host.
Assisted-by: Claude Opus 5
2026-09-03 01:36:39 -04:00
|
|
|
env=system_env(),
|
[ADD] qemu menu : récupérer des fichiers dans le disque d'une VM
Une VM qui ne démarre plus garde ses fichiers : libguestfs monte son
qcow2 sans elle. Toute commande porte « --ro », et c'est ce qui change la
manœuvre : ouvrir en écriture le disque d'une machine allumée corrompt
son système de fichiers, la lire ne risque rien. Un arrêt reste proposé
et non imposé — une lecture vivante voit un état peut-être incohérent,
un fichier à moitié écrit, un journal non rejoué. Le nom du paquet suit
la distribution, celui de Debian n'existant nulle part ailleurs.
Vérifié : 16 tests, rougis par quatre mutations — perdre le « --ro »,
donner le nom Debian partout, dupliquer un numéro, retirer l'entrée.
--- EN ---
A VM that no longer boots still holds its files: libguestfs mounts its
qcow2 without it. Every command carries « --ro », and that is what
changes the operation: opening a running machine's disk for writing
corrupts its filesystem, reading it risks nothing. A shutdown stays
offered, not imposed — a live read sees a possibly torn state, a
half-written file, an unreplayed journal. The package name follows the
distribution, Debian's existing nowhere else.
Checked: 16 tests, turned red by four mutations — losing « --ro », using
Debian's name everywhere, duplicating a number, dropping the entry.
Assisted-by: Claude Opus 5
2026-09-03 01:23:34 -04:00
|
|
|
)
|
|
|
|
|
except (OSError, subprocess.SubprocessError):
|
|
|
|
|
return []
|
|
|
|
|
if res.returncode != 0:
|
|
|
|
|
if res.stderr:
|
|
|
|
|
print(f" ⚠ {res.stderr.strip()[:300]}")
|
|
|
|
|
return []
|
|
|
|
|
return [ln for ln in (res.stdout or "").splitlines() if ln.strip()]
|
|
|
|
|
|
|
|
|
|
def _qemu_recover_ensure_tools(self):
|
|
|
|
|
"""guestfish est-il là ? Sinon, proposer de l'installer. Rend un bool.
|
|
|
|
|
|
|
|
|
|
L'installation est PROPOSÉE et la commande montrée avant : elle pose
|
|
|
|
|
un paquet de plusieurs centaines de mégaoctets, avec une image noyau.
|
|
|
|
|
"""
|
|
|
|
|
manque_essentiel = shutil.which(GUESTFS_BIN_ESSENTIEL) is None
|
|
|
|
|
confort = [b for b in GUESTFS_BIN_CONFORT if shutil.which(b) is None]
|
|
|
|
|
if not manque_essentiel and not confort:
|
|
|
|
|
return True
|
|
|
|
|
install, paquet = guestfs_install_cmd()
|
|
|
|
|
if manque_essentiel:
|
|
|
|
|
print(f"\n⚠ {t('guestfish is missing: libguestfs is not here.')}")
|
|
|
|
|
else:
|
|
|
|
|
print(f"\n⚠ {t('Some libguestfs helpers are missing:')}")
|
|
|
|
|
print(f" {' '.join(confort)}")
|
|
|
|
|
if not install:
|
|
|
|
|
print(f" {t('Unknown package manager: install libguestfs.')}")
|
|
|
|
|
return not manque_essentiel
|
|
|
|
|
print(f" {t('Will execute:')} {install}")
|
|
|
|
|
if not self._is_yes_default_yes(
|
|
|
|
|
input(t("Install libguestfs now? (Y/n): "))
|
|
|
|
|
):
|
|
|
|
|
return not manque_essentiel
|
[FIX] qemu menu : sortir le venv du PATH des outils système
Le « bin » du venv est en tête du PATH de chaque commande lancée par le
menu, et il contient un python3. Un outil système écrit en Python et
amorcé par « env python3 » s'y amorce donc, dans un interpréteur où les
modules de la distribution n'existent pas : l'import échoue sur un module
que la machine possède pourtant. Sous sudo le piège était invisible, sudo
réinitialisant le PATH ; le retirer là où il ne servait plus l'a mis au
jour.
Vérifié : 6 tests, rougis par trois mutations. La ligne d'amorçage des
outils visés n'a pas été inspectée : le mécanisme est démontré, pas qu'il
soit la cause sur un hôte donné.
--- EN ---
The venv's « bin » leads the PATH of every command the menu launches, and
it holds a python3. A system tool written in Python and started through
« env python3 » therefore boots on that interpreter, where the
distribution's modules do not exist: the import fails on a module the
machine does have. Under sudo the trap was invisible, sudo resetting the
PATH; removing it where it was no longer needed brought it out.
Checked: 6 tests, turned red by three mutations. The shebang of the tools
concerned was not inspected: the mechanism is demonstrated, not that it
is the cause on any given host.
Assisted-by: Claude Opus 5
2026-09-03 01:36:39 -04:00
|
|
|
self.execute.exec_command_live(
|
|
|
|
|
install, source_erplibre=False, new_env={"PATH": system_path()}
|
|
|
|
|
)
|
[ADD] qemu menu : récupérer des fichiers dans le disque d'une VM
Une VM qui ne démarre plus garde ses fichiers : libguestfs monte son
qcow2 sans elle. Toute commande porte « --ro », et c'est ce qui change la
manœuvre : ouvrir en écriture le disque d'une machine allumée corrompt
son système de fichiers, la lire ne risque rien. Un arrêt reste proposé
et non imposé — une lecture vivante voit un état peut-être incohérent,
un fichier à moitié écrit, un journal non rejoué. Le nom du paquet suit
la distribution, celui de Debian n'existant nulle part ailleurs.
Vérifié : 16 tests, rougis par quatre mutations — perdre le « --ro »,
donner le nom Debian partout, dupliquer un numéro, retirer l'entrée.
--- EN ---
A VM that no longer boots still holds its files: libguestfs mounts its
qcow2 without it. Every command carries « --ro », and that is what
changes the operation: opening a running machine's disk for writing
corrupts its filesystem, reading it risks nothing. A shutdown stays
offered, not imposed — a live read sees a possibly torn state, a
half-written file, an unreplayed journal. The package name follows the
distribution, Debian's existing nowhere else.
Checked: 16 tests, turned red by four mutations — losing « --ro », using
Debian's name everywhere, duplicating a number, dropping the entry.
Assisted-by: Claude Opus 5
2026-09-03 01:23:34 -04:00
|
|
|
return shutil.which(GUESTFS_BIN_ESSENTIEL) is not None
|
|
|
|
|
|
|
|
|
|
def _qemu_recover_ready(self, name):
|
|
|
|
|
"""La VM est-elle dans un état où la lecture a du sens ? Rend un bool.
|
|
|
|
|
|
|
|
|
|
Une VM ALLUMÉE écrit pendant qu'on lit : la copie voit un instantané
|
|
|
|
|
qui peut être incohérent — un fichier à moitié écrit, un journal non
|
|
|
|
|
rejoué. Ce n'est pas dangereux en « --ro », mais il faut le dire, et
|
|
|
|
|
proposer l'arrêt propre qui rend la lecture fidèle.
|
|
|
|
|
"""
|
|
|
|
|
etat = self._qemu_domstate(name)
|
|
|
|
|
if etat != "running":
|
|
|
|
|
return True
|
|
|
|
|
print(f"\n⚠ {t('This VM is running.')}")
|
|
|
|
|
print(f" {t('Reading a live disk sees a possibly torn state:')}")
|
|
|
|
|
print(f" {t('a half-written file, an unreplayed journal.')}")
|
|
|
|
|
choix = self._qemu_pick(
|
|
|
|
|
t("What do you want to do?"),
|
|
|
|
|
["read", "shutdown", "cancel"],
|
|
|
|
|
"shutdown",
|
|
|
|
|
[
|
|
|
|
|
t("Read anyway (read-only, no risk for the VM)"),
|
|
|
|
|
t("Shut the VM down cleanly, then read"),
|
|
|
|
|
t("Cancel"),
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
if choix == "cancel":
|
|
|
|
|
return False
|
|
|
|
|
if choix == "read":
|
|
|
|
|
return True
|
|
|
|
|
return self._qemu_shutdown_wait(name)
|
|
|
|
|
|
|
|
|
|
def _qemu_recover_pick_filesystem(self, disk):
|
|
|
|
|
"""Système de fichiers choisi dans le disque, ou "" si aucun.
|
|
|
|
|
|
|
|
|
|
« list-filesystems » les rend tous, y compris ceux qu'on ne peut pas
|
|
|
|
|
monter — swap, partitions vides. Les montrer quand même : leur absence
|
|
|
|
|
de la liste inquiéterait plus qu'elle n'aiderait.
|
|
|
|
|
"""
|
|
|
|
|
cmd = self._qemu_guestfish_cmd(disk, "run", "list-filesystems")
|
|
|
|
|
print(f"\n{t('Will execute:')} {cmd}")
|
|
|
|
|
lignes = self._qemu_guestfish_lines(disk, "run", "list-filesystems")
|
|
|
|
|
if not lignes:
|
|
|
|
|
print(f" {t('No filesystem found on this disk.')}")
|
|
|
|
|
return ""
|
|
|
|
|
parts, labels = [], []
|
|
|
|
|
for ligne in lignes:
|
|
|
|
|
# « /dev/sda3: ext4 » — le nom, puis son type.
|
|
|
|
|
dev, _, typ = ligne.partition(":")
|
|
|
|
|
dev, typ = dev.strip(), typ.strip()
|
|
|
|
|
if not dev:
|
|
|
|
|
continue
|
|
|
|
|
parts.append(dev)
|
|
|
|
|
labels.append(f"{dev} {typ}")
|
|
|
|
|
if not parts:
|
|
|
|
|
return ""
|
|
|
|
|
# Le plus grand système de fichiers non-swap est presque toujours la
|
|
|
|
|
# racine : le proposer par défaut épargne un choix à qui ne connaît
|
|
|
|
|
# pas le partitionnement de sa VM.
|
|
|
|
|
defaut = next(
|
|
|
|
|
(p for p, lab in zip(parts, labels) if "swap" not in lab),
|
|
|
|
|
parts[0],
|
|
|
|
|
)
|
|
|
|
|
return self._qemu_pick(t("Filesystem to mount"), parts, defaut, labels)
|
|
|
|
|
|
|
|
|
|
def _qemu_recover_browse(self, disk, part, chemin):
|
|
|
|
|
"""Liste un répertoire du système de fichiers monté.
|
|
|
|
|
|
|
|
|
|
Rend les entrées trouvées."""
|
|
|
|
|
cmd = self._qemu_guestfish_cmd(
|
|
|
|
|
disk, "run", f"mount {part} /", f"ls {shlex.quote(chemin)}"
|
|
|
|
|
)
|
|
|
|
|
print(f"\n{t('Will execute:')} {cmd}")
|
|
|
|
|
entrees = self._qemu_guestfish_lines(
|
|
|
|
|
disk, "run", f"mount {part} /", f"ls {shlex.quote(chemin)}"
|
|
|
|
|
)
|
|
|
|
|
if not entrees:
|
|
|
|
|
print(f" {t('Empty or unreadable directory.')}")
|
|
|
|
|
return []
|
|
|
|
|
for entree in entrees:
|
|
|
|
|
print(f" {entree}")
|
|
|
|
|
print(f" {t('Total:')} {len(entrees)}")
|
|
|
|
|
return entrees
|
|
|
|
|
|
|
|
|
|
def _qemu_recover_copy_out(self, disk, part, source, dest):
|
|
|
|
|
"""Extrait un chemin de la VM vers l'hôte. Rend un bool.
|
|
|
|
|
|
|
|
|
|
« copy-out » écrit DANS le répertoire de destination : il faut donc
|
|
|
|
|
qu'il existe, sinon guestfish s'arrête sur une erreur qui ne dit pas
|
|
|
|
|
laquelle des deux extrémités manque.
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
os.makedirs(dest, exist_ok=True)
|
|
|
|
|
except OSError as exc:
|
|
|
|
|
print(f" ⚠ {t('Cannot create the destination: ')}{exc}")
|
|
|
|
|
return False
|
|
|
|
|
cmd = self._qemu_guestfish_cmd(
|
|
|
|
|
disk,
|
|
|
|
|
"run",
|
|
|
|
|
f"mount {part} /",
|
|
|
|
|
f"copy-out {shlex.quote(source)} {shlex.quote(dest)}",
|
|
|
|
|
)
|
|
|
|
|
print(f"\n{t('Will execute:')} {cmd}")
|
[FIX] qemu menu : sortir le venv du PATH des outils système
Le « bin » du venv est en tête du PATH de chaque commande lancée par le
menu, et il contient un python3. Un outil système écrit en Python et
amorcé par « env python3 » s'y amorce donc, dans un interpréteur où les
modules de la distribution n'existent pas : l'import échoue sur un module
que la machine possède pourtant. Sous sudo le piège était invisible, sudo
réinitialisant le PATH ; le retirer là où il ne servait plus l'a mis au
jour.
Vérifié : 6 tests, rougis par trois mutations. La ligne d'amorçage des
outils visés n'a pas été inspectée : le mécanisme est démontré, pas qu'il
soit la cause sur un hôte donné.
--- EN ---
The venv's « bin » leads the PATH of every command the menu launches, and
it holds a python3. A system tool written in Python and started through
« env python3 » therefore boots on that interpreter, where the
distribution's modules do not exist: the import fails on a module the
machine does have. Under sudo the trap was invisible, sudo resetting the
PATH; removing it where it was no longer needed brought it out.
Checked: 6 tests, turned red by three mutations. The shebang of the tools
concerned was not inspected: the mechanism is demonstrated, not that it
is the cause on any given host.
Assisted-by: Claude Opus 5
2026-09-03 01:36:39 -04:00
|
|
|
code = self.execute.exec_command_live(
|
|
|
|
|
cmd, source_erplibre=False, new_env={"PATH": system_path()}
|
|
|
|
|
)
|
[ADD] qemu menu : récupérer des fichiers dans le disque d'une VM
Une VM qui ne démarre plus garde ses fichiers : libguestfs monte son
qcow2 sans elle. Toute commande porte « --ro », et c'est ce qui change la
manœuvre : ouvrir en écriture le disque d'une machine allumée corrompt
son système de fichiers, la lire ne risque rien. Un arrêt reste proposé
et non imposé — une lecture vivante voit un état peut-être incohérent,
un fichier à moitié écrit, un journal non rejoué. Le nom du paquet suit
la distribution, celui de Debian n'existant nulle part ailleurs.
Vérifié : 16 tests, rougis par quatre mutations — perdre le « --ro »,
donner le nom Debian partout, dupliquer un numéro, retirer l'entrée.
--- EN ---
A VM that no longer boots still holds its files: libguestfs mounts its
qcow2 without it. Every command carries « --ro », and that is what
changes the operation: opening a running machine's disk for writing
corrupts its filesystem, reading it risks nothing. A shutdown stays
offered, not imposed — a live read sees a possibly torn state, a
half-written file, an unreplayed journal. The package name follows the
distribution, Debian's existing nowhere else.
Checked: 16 tests, turned red by four mutations — losing « --ro », using
Debian's name everywhere, duplicating a number, dropping the entry.
Assisted-by: Claude Opus 5
2026-09-03 01:23:34 -04:00
|
|
|
if code:
|
|
|
|
|
print(f" ⚠ {t('Extraction failed.')}")
|
|
|
|
|
return False
|
|
|
|
|
cible = os.path.join(dest, os.path.basename(source.rstrip("/")))
|
|
|
|
|
print(f"\n✅ {t('Extracted to: ')}{cible}")
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
def _qemu_recover_diagnostics(self, disk):
|
|
|
|
|
"""Commandes de diagnostic sur le disque, sans rien monter à la main.
|
|
|
|
|
|
|
|
|
|
Chacune répond à une question précise qu'on se pose quand la lecture
|
|
|
|
|
ne donne pas ce qu'on attend : les partitions existent-elles, restent-
|
|
|
|
|
elles de la place, le système est-il reconnu, l'appliance
|
|
|
|
|
démarre-t-elle.
|
|
|
|
|
"""
|
|
|
|
|
q = shlex.quote(str(disk))
|
|
|
|
|
sondes = [
|
|
|
|
|
(
|
|
|
|
|
t("Partitions and sizes"),
|
|
|
|
|
f"virt-filesystems -a {q} --long -h --all",
|
|
|
|
|
),
|
|
|
|
|
(t("Free space per filesystem"), f"virt-df -a {q} -h"),
|
|
|
|
|
(
|
|
|
|
|
t("Detected operating system"),
|
|
|
|
|
self._qemu_guestfish_cmd(disk, "run", "inspect-os"),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
t("Does the libguestfs appliance boot?"),
|
|
|
|
|
"libguestfs-test-tool",
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
for titre, cmd in sondes:
|
|
|
|
|
print(f"\n── {titre} ──")
|
|
|
|
|
print(f"{t('Will execute:')} {cmd}")
|
[FIX] qemu menu : sortir le venv du PATH des outils système
Le « bin » du venv est en tête du PATH de chaque commande lancée par le
menu, et il contient un python3. Un outil système écrit en Python et
amorcé par « env python3 » s'y amorce donc, dans un interpréteur où les
modules de la distribution n'existent pas : l'import échoue sur un module
que la machine possède pourtant. Sous sudo le piège était invisible, sudo
réinitialisant le PATH ; le retirer là où il ne servait plus l'a mis au
jour.
Vérifié : 6 tests, rougis par trois mutations. La ligne d'amorçage des
outils visés n'a pas été inspectée : le mécanisme est démontré, pas qu'il
soit la cause sur un hôte donné.
--- EN ---
The venv's « bin » leads the PATH of every command the menu launches, and
it holds a python3. A system tool written in Python and started through
« env python3 » therefore boots on that interpreter, where the
distribution's modules do not exist: the import fails on a module the
machine does have. Under sudo the trap was invisible, sudo resetting the
PATH; removing it where it was no longer needed brought it out.
Checked: 6 tests, turned red by three mutations. The shebang of the tools
concerned was not inspected: the mechanism is demonstrated, not that it
is the cause on any given host.
Assisted-by: Claude Opus 5
2026-09-03 01:36:39 -04:00
|
|
|
self.execute.exec_command_live(
|
|
|
|
|
cmd, source_erplibre=False, new_env={"PATH": system_path()}
|
|
|
|
|
)
|
[ADD] qemu menu : récupérer des fichiers dans le disque d'une VM
Une VM qui ne démarre plus garde ses fichiers : libguestfs monte son
qcow2 sans elle. Toute commande porte « --ro », et c'est ce qui change la
manœuvre : ouvrir en écriture le disque d'une machine allumée corrompt
son système de fichiers, la lire ne risque rien. Un arrêt reste proposé
et non imposé — une lecture vivante voit un état peut-être incohérent,
un fichier à moitié écrit, un journal non rejoué. Le nom du paquet suit
la distribution, celui de Debian n'existant nulle part ailleurs.
Vérifié : 16 tests, rougis par quatre mutations — perdre le « --ro »,
donner le nom Debian partout, dupliquer un numéro, retirer l'entrée.
--- EN ---
A VM that no longer boots still holds its files: libguestfs mounts its
qcow2 without it. Every command carries « --ro », and that is what
changes the operation: opening a running machine's disk for writing
corrupts its filesystem, reading it risks nothing. A shutdown stays
offered, not imposed — a live read sees a possibly torn state, a
half-written file, an unreplayed journal. The package name follows the
distribution, Debian's existing nowhere else.
Checked: 16 tests, turned red by four mutations — losing « --ro », using
Debian's name everywhere, duplicating a number, dropping the entry.
Assisted-by: Claude Opus 5
2026-09-03 01:23:34 -04:00
|
|
|
|
|
|
|
|
def _qemu_recover_files(self):
|
|
|
|
|
"""Récupère des fichiers dans le disque d'une VM, sans la démarrer."""
|
|
|
|
|
print(f"\n💾 {t('Recover files from a VM disk (libguestfs)')}")
|
|
|
|
|
if not self._qemu_recover_ensure_tools():
|
|
|
|
|
return
|
|
|
|
|
self._qemu_list_vms()
|
|
|
|
|
print()
|
|
|
|
|
name = input(t("VM name or ID: ")).strip()
|
|
|
|
|
if not name:
|
|
|
|
|
print(t("VM name is required!"))
|
|
|
|
|
return
|
|
|
|
|
name = self._qemu_domname(name)
|
|
|
|
|
disk = self._qemu_main_disk(name)
|
|
|
|
|
if not disk:
|
|
|
|
|
print(f" ⚠ {t('No disk found for this VM.')}")
|
|
|
|
|
return
|
|
|
|
|
print(f" {t('Disk:')} {disk}")
|
|
|
|
|
if not self._qemu_recover_ready(name):
|
|
|
|
|
print(t("Cancelled."))
|
|
|
|
|
return
|
|
|
|
|
if self._is_yes(input(t("Run the diagnostics first? (y/N): "))):
|
|
|
|
|
self._qemu_recover_diagnostics(disk)
|
|
|
|
|
part = self._qemu_recover_pick_filesystem(disk)
|
|
|
|
|
if not part:
|
|
|
|
|
return
|
|
|
|
|
chemin = input(t("Directory to list [/]: ")).strip() or "/"
|
|
|
|
|
while True:
|
|
|
|
|
self._qemu_recover_browse(disk, part, chemin)
|
|
|
|
|
suite = input(
|
|
|
|
|
t("Another directory, or Enter to extract: ")
|
|
|
|
|
).strip()
|
|
|
|
|
if not suite:
|
|
|
|
|
break
|
|
|
|
|
chemin = suite
|
|
|
|
|
source = input(f"{t('Path to extract')} [{chemin}] : ").strip()
|
|
|
|
|
source = source or chemin
|
|
|
|
|
defaut_dest = f"/tmp/{name}-backup"
|
|
|
|
|
dest = input(f"{t('Destination on the host')} [{defaut_dest}] : ")
|
|
|
|
|
dest = dest.strip() or defaut_dest
|
|
|
|
|
self._qemu_recover_copy_out(disk, part, source, dest)
|