[FIX] script todo: réduction disque SÛRE via virt-resize (ne casse plus l'OS)
Réduire avec « qemu-img resize --shrink » tronquait le conteneur qcow2 SANS réduire le FS/partition/GPT invités : partition racine tronquée + GPT de secours perdue -> dracut-initqueue en échec, OS non bootable. Désormais la réduction passe par virt-resize (libguestfs) qui réduit proprement système de fichiers + partition + GPT : - _qemu_safe_shrink : écrit dans une NOUVELLE image (qemu-img create + virt-resize --shrink <partition>), et ne remplace l'originale (mv) QUE si virt-resize réussit. En cas d'échec/refus, le disque d'origine reste INTACT (impossible de corrompre). Sauvegarde conservée en .bak. - _qemu_largest_partition : détecte la partition racine (la plus grosse) via virt-filesystems. - _qemu_install_libguestfs : propose d'installer libguestfs-tools si absent ; sinon on ABANDONNE (on ne tronque jamais). - _qemu_offer_start extrait (redémarrage après extinction). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7e89fa354f
commit
9b00de6d8b
2 changed files with 155 additions and 26 deletions
|
|
@ -1625,6 +1625,7 @@ class TODO:
|
|||
|
||||
# 3) Application selon agrandir/réduire et l'état de la VM.
|
||||
was_shut_down = False # la VM a-t-elle été éteinte pour l'occasion ?
|
||||
cmd = None # commande d'AGRANDISSEMENT (la réduction a son propre flux)
|
||||
if shrink:
|
||||
# DANGER : qcow2 --shrink ne réduit PAS le FS invité -> perte de
|
||||
# données si le FS dépasse la cible. VM éteinte obligatoire.
|
||||
|
|
@ -1651,10 +1652,15 @@ class TODO:
|
|||
):
|
||||
print(t("Cancelled."))
|
||||
return
|
||||
cmd = (
|
||||
f"sudo qemu-img resize --shrink {shlex.quote(disk)} "
|
||||
f"{new_gb:g}G"
|
||||
)
|
||||
# Réduction SÛRE via virt-resize (réduit FS + partition + GPT).
|
||||
# On opère sur une COPIE : l'original n'est remplacé qu'en cas de
|
||||
# SUCCÈS -> aucune corruption possible. (Le brut « qemu-img resize
|
||||
# --shrink » tronquait le conteneur sans réduire le FS/partition
|
||||
# -> GPT de secours perdue, racine tronquée -> dracut-initqueue en
|
||||
# échec, OS non bootable.)
|
||||
if not self._qemu_safe_shrink(name, disk, new_gb):
|
||||
self._qemu_offer_start(name, was_shut_down)
|
||||
return
|
||||
elif state == "running":
|
||||
# Agrandissement À CHAUD : le disque virtuel grossit, le FS invité
|
||||
# devra être étendu ensuite.
|
||||
|
|
@ -1665,30 +1671,125 @@ class TODO:
|
|||
else:
|
||||
cmd = f"sudo qemu-img resize {shlex.quote(disk)} {new_gb:g}G"
|
||||
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
if self.execute.exec_command_live(cmd, source_erplibre=False) != 0:
|
||||
print(f"❌ {t('Resize failed (see error above).')}")
|
||||
return
|
||||
print(f"✅ {t('Virtual disk resized.')}")
|
||||
|
||||
# 4) Étendre le FS invité (agrandissement seulement, VM joignable).
|
||||
if not shrink and self._is_yes(
|
||||
input(t("Grow the guest filesystem now (over SSH)? (y/N): "))
|
||||
):
|
||||
self._qemu_grow_guest_fs(name)
|
||||
|
||||
# 5) La VM a été éteinte pour l'opération : le noter et proposer de la
|
||||
# redémarrer (sinon, on ne demande rien).
|
||||
if was_shut_down:
|
||||
print(f"\nℹ {t('The VM was shut down for the resize.')}")
|
||||
# 4) Agrandissement : exécuter la commande + proposer d'étendre le FS.
|
||||
if cmd is not None:
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
if self.execute.exec_command_live(cmd, source_erplibre=False) != 0:
|
||||
print(f"❌ {t('Resize failed (see error above).')}")
|
||||
return
|
||||
print(f"✅ {t('Virtual disk resized.')}")
|
||||
if self._is_yes(
|
||||
input(t("Start the VM now? (y/N): "))
|
||||
input(t("Grow the guest filesystem now (over SSH)? (y/N): "))
|
||||
):
|
||||
# `name` est déjà le nom canonique (résolu au début) : un
|
||||
# « virsh start <id> » échouerait car l'ID a disparu.
|
||||
cmd = f"sudo virsh start {shlex.quote(name)}"
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(cmd, source_erplibre=False)
|
||||
self._qemu_grow_guest_fs(name)
|
||||
|
||||
# 5) La VM a été éteinte pour l'opération : proposer de la redémarrer.
|
||||
self._qemu_offer_start(name, was_shut_down)
|
||||
|
||||
def _qemu_offer_start(self, name, was_shut_down):
|
||||
"""Si la VM a été éteinte pour l'opération, le noter et proposer de la
|
||||
redémarrer (sinon ne rien demander)."""
|
||||
if not was_shut_down:
|
||||
return
|
||||
print(f"\nℹ {t('The VM was shut down for the resize.')}")
|
||||
if self._is_yes(input(t("Start the VM now? (y/N): "))):
|
||||
# `name` est déjà le nom canonique : « virsh start <id> »
|
||||
# échouerait car l'ID disparaît quand la VM est éteinte.
|
||||
cmd = f"sudo virsh start {shlex.quote(name)}"
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(cmd, source_erplibre=False)
|
||||
|
||||
def _qemu_safe_shrink(self, name, disk, new_gb):
|
||||
"""Réduit le disque SANS casser l'OS : virt-resize (libguestfs) réduit
|
||||
le système de fichiers + la partition + la GPT. On écrit dans une
|
||||
NOUVELLE image ; l'originale n'est remplacée qu'en cas de succès (en cas
|
||||
d'échec/refus, le disque d'origine est laissé INTACT). Renvoie True si
|
||||
la réduction a réussi."""
|
||||
if not (
|
||||
shutil.which("virt-resize") and shutil.which("virt-filesystems")
|
||||
):
|
||||
print(f"\n{t('Safe shrink needs libguestfs (virt-resize).')}")
|
||||
if not self._is_yes(
|
||||
input(t("Install libguestfs-tools now? (y/N): "))
|
||||
) or not self._qemu_install_libguestfs():
|
||||
print(t("Shrink aborted; disk left intact."))
|
||||
return False
|
||||
part = self._qemu_largest_partition(disk)
|
||||
if not part:
|
||||
print(t("Could not detect the partition to shrink; aborting."))
|
||||
return False
|
||||
new_disk = f"{disk}.resized"
|
||||
bak = f"{disk}.bak"
|
||||
subprocess.run(["sudo", "rm", "-f", new_disk], check=False)
|
||||
print(
|
||||
f"\n{t('Shrinking guest FS + partition via virt-resize')} "
|
||||
f"({part} -> {new_gb:g}G)…"
|
||||
)
|
||||
create = subprocess.run(
|
||||
["sudo", "qemu-img", "create", "-f", "qcow2", new_disk,
|
||||
f"{new_gb:g}G"]
|
||||
)
|
||||
if create.returncode != 0:
|
||||
print(f"❌ {t('Resize failed (see error above).')}")
|
||||
subprocess.run(["sudo", "rm", "-f", new_disk], check=False)
|
||||
return False
|
||||
# virt-resize copie old -> new en réduisant le FS de `part`.
|
||||
rc = subprocess.run(
|
||||
["sudo", "virt-resize", "--shrink", part, disk, new_disk]
|
||||
).returncode
|
||||
if rc != 0:
|
||||
print(f"❌ {t('virt-resize failed; original disk left intact.')}")
|
||||
subprocess.run(["sudo", "rm", "-f", new_disk], check=False)
|
||||
return False
|
||||
# Succès : on sauvegarde l'original et on met la nouvelle image au
|
||||
# MÊME chemin (la définition libvirt reste valide).
|
||||
subprocess.run(["sudo", "mv", "-f", disk, bak], check=False)
|
||||
subprocess.run(["sudo", "mv", "-f", new_disk, disk], check=False)
|
||||
print(f"✅ {t('Disk safely shrunk. Backup kept at:')} {bak}")
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _qemu_largest_partition(disk):
|
||||
"""Nom (côté invité, ex. /dev/sda1) de la plus GROSSE partition du
|
||||
disque via virt-filesystems — celle qui porte la racine à réduire."""
|
||||
try:
|
||||
res = subprocess.run(
|
||||
["sudo", "virt-filesystems", "-a", disk, "--partitions",
|
||||
"--long", "-b"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=180,
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return None
|
||||
best, best_sz = None, -1
|
||||
for line in res.stdout.splitlines():
|
||||
parts = line.split()
|
||||
if not parts or not parts[0].startswith("/dev/"):
|
||||
continue
|
||||
# Colonnes : Name Type MBR Size Parent -> Size = plus grand entier.
|
||||
size = max(
|
||||
(int(p) for p in parts if p.isdigit()), default=-1
|
||||
)
|
||||
if size > best_sz:
|
||||
best, best_sz = parts[0], size
|
||||
return best
|
||||
|
||||
def _qemu_install_libguestfs(self):
|
||||
"""Installe libguestfs-tools (apt/dnf/pacman). Renvoie True si
|
||||
virt-resize est ensuite disponible."""
|
||||
if shutil.which("apt-get"):
|
||||
cmd = "sudo apt-get install -y libguestfs-tools"
|
||||
elif shutil.which("dnf"):
|
||||
cmd = "sudo dnf install -y guestfs-tools libguestfs-tools"
|
||||
elif shutil.which("pacman"):
|
||||
cmd = "sudo pacman -S --needed --noconfirm libguestfs"
|
||||
else:
|
||||
print(t("Unknown package manager; install it manually."))
|
||||
return False
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
os.system(cmd)
|
||||
return shutil.which("virt-resize") is not None
|
||||
|
||||
# Commande d'extension du FS racine (partition + FS) réutilisée par SSH
|
||||
# et par le repli console série.
|
||||
|
|
|
|||
|
|
@ -1424,6 +1424,34 @@ TRANSLATIONS = {
|
|||
"fr": "Impossible de lire la taille actuelle du disque ; abandon.",
|
||||
"en": "Could not read current disk size; aborting.",
|
||||
},
|
||||
"Safe shrink needs libguestfs (virt-resize).": {
|
||||
"fr": "La réduction sûre nécessite libguestfs (virt-resize).",
|
||||
"en": "Safe shrink needs libguestfs (virt-resize).",
|
||||
},
|
||||
"Install libguestfs-tools now? (y/N): ": {
|
||||
"fr": "Installer libguestfs-tools maintenant ? (o/N) : ",
|
||||
"en": "Install libguestfs-tools now? (y/N): ",
|
||||
},
|
||||
"Shrink aborted; disk left intact.": {
|
||||
"fr": "Réduction annulée ; disque laissé intact.",
|
||||
"en": "Shrink aborted; disk left intact.",
|
||||
},
|
||||
"Could not detect the partition to shrink; aborting.": {
|
||||
"fr": "Partition à réduire introuvable ; abandon.",
|
||||
"en": "Could not detect the partition to shrink; aborting.",
|
||||
},
|
||||
"Shrinking guest FS + partition via virt-resize": {
|
||||
"fr": "Réduction du FS invité + partition via virt-resize",
|
||||
"en": "Shrinking guest FS + partition via virt-resize",
|
||||
},
|
||||
"virt-resize failed; original disk left intact.": {
|
||||
"fr": "virt-resize a échoué ; disque d'origine laissé intact.",
|
||||
"en": "virt-resize failed; original disk left intact.",
|
||||
},
|
||||
"Disk safely shrunk. Backup kept at:": {
|
||||
"fr": "Disque réduit en toute sécurité. Sauvegarde conservée :",
|
||||
"en": "Disk safely shrunk. Backup kept at:",
|
||||
},
|
||||
"click to expand": {
|
||||
"fr": "cliquer pour déplier",
|
||||
"en": "click to expand",
|
||||
|
|
|
|||
Loading…
Reference in a new issue