Compare commits
No commits in common. "4f0c3cd2175827e2faa297caa19b6b32977e27cd" and "7e89fa354fa7ca9989523a7a1d02e9fbf21f9907" have entirely different histories.
4f0c3cd217
...
7e89fa354f
3 changed files with 42 additions and 259 deletions
|
|
@ -545,7 +545,6 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
DataTable { width: 74; height: 1fr; overflow-x: auto; border: solid $accent; }
|
DataTable { width: 74; height: 1fr; overflow-x: auto; border: solid $accent; }
|
||||||
RichLog { border: solid $accent; }
|
RichLog { border: solid $accent; }
|
||||||
#telemetry { height: 1; color: $text-muted; }
|
#telemetry { height: 1; color: $text-muted; }
|
||||||
#autobar { height: 1; color: $warning; }
|
|
||||||
#stats { height: 1; color: $accent; }
|
#stats { height: 1; color: $accent; }
|
||||||
#statsdetail { display: none; height: auto; color: $text-muted; }
|
#statsdetail { display: none; height: auto; color: $text-muted; }
|
||||||
#sshbar { height: 2; color: $text-muted; }
|
#sshbar { height: 2; color: $text-muted; }
|
||||||
|
|
@ -564,8 +563,6 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
("f", "follow", "Suivre"),
|
("f", "follow", "Suivre"),
|
||||||
("c", "copy_log", "Copier log"),
|
("c", "copy_log", "Copier log"),
|
||||||
("d", "details", "Détails erreurs"),
|
("d", "details", "Détails erreurs"),
|
||||||
("a", "toggle_auto", "Auto-refresh on/off"),
|
|
||||||
("r", "refresh_now", "Rafraîchir"),
|
|
||||||
("p", "pause_all", "Pause tout"),
|
("p", "pause_all", "Pause tout"),
|
||||||
("o", "resume_all", "Reprendre tout"),
|
("o", "resume_all", "Reprendre tout"),
|
||||||
]
|
]
|
||||||
|
|
@ -591,11 +588,6 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
self._errcount = {}
|
self._errcount = {}
|
||||||
# Sommaire de stats déplié (clic) ou non.
|
# Sommaire de stats déplié (clic) ou non.
|
||||||
self._stats_open = False
|
self._stats_open = False
|
||||||
# Rafraîchissement automatique COUPÉ par défaut : sur un gros parc,
|
|
||||||
# les ticks périodiques (logs/table/domstate) causent du lag quand
|
|
||||||
# on lit les logs. On l'active à la demande (touche « a ») ; sinon
|
|
||||||
# « r » rafraîchit une seule fois.
|
|
||||||
self._auto_refresh = False
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _fmt(secs):
|
def _fmt(secs):
|
||||||
|
|
@ -633,8 +625,6 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
with Horizontal():
|
with Horizontal():
|
||||||
yield table
|
yield table
|
||||||
yield self._log
|
yield self._log
|
||||||
# Indicateur d'auto-refresh (a = on/off · r = rafraîchir).
|
|
||||||
yield Static("", id="autobar")
|
|
||||||
# Barre de télémétrie hôte (CPU, disque, ETA parc).
|
# Barre de télémétrie hôte (CPU, disque, ETA parc).
|
||||||
yield Static("", id="telemetry")
|
yield Static("", id="telemetry")
|
||||||
# Sommaire de stats en CHIFFRES (cliquable -> détail).
|
# Sommaire de stats en CHIFFRES (cliquable -> détail).
|
||||||
|
|
@ -650,17 +640,15 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
self.sub_title = f"0/{len(vms)} {t('completed')}"
|
self.sub_title = f"0/{len(vms)} {t('completed')}"
|
||||||
self._refresh_ssh()
|
self._refresh_ssh()
|
||||||
self._load_selected_log(reset=True)
|
self._load_selected_log(reset=True)
|
||||||
self._update_autobar()
|
# Table toutes les 2 s (30 lectures de fin de log), suivi du log
|
||||||
# Ticks périodiques : ils ne font RIEN tant que l'auto-refresh est
|
# sélectionné toutes les 1 s (une seule lecture incrémentale).
|
||||||
# coupé (wrappers gardés). On garde les intervalles en place pour
|
|
||||||
# ne pas avoir à les (re)créer au moment du toggle.
|
|
||||||
self.set_interval(2.0, self._tick_table)
|
self.set_interval(2.0, self._tick_table)
|
||||||
self.set_interval(1.0, self._tick_log)
|
self.set_interval(1.0, self._tick_log)
|
||||||
|
# État libvirt (pause / effacée) : check LENT (appel virsh) toutes
|
||||||
|
# les 10 s ; le tableau applique le cache à chaque tick (2 s).
|
||||||
self.set_interval(10.0, self._tick_domstate)
|
self.set_interval(10.0, self._tick_domstate)
|
||||||
# Un SEUL relevé initial (table + domstate) pour afficher l'état
|
# Premier relevé domstate immédiat (async -> via worker).
|
||||||
# courant à l'ouverture, même auto-refresh coupé.
|
self.run_worker(self._tick_domstate(), exclusive=False)
|
||||||
self.run_worker(self._do_domstate(), exclusive=False)
|
|
||||||
self.run_worker(self._do_table(), exclusive=False)
|
|
||||||
|
|
||||||
# -- helpers -------------------------------------------------------- #
|
# -- helpers -------------------------------------------------------- #
|
||||||
def _vm_by_name(self, name):
|
def _vm_by_name(self, name):
|
||||||
|
|
@ -673,7 +661,6 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
bar.update(
|
bar.update(
|
||||||
f" {vm['ssh']} (s = SSH · w = web :8069 · "
|
f" {vm['ssh']} (s = SSH · w = web :8069 · "
|
||||||
"c = copier le log · d = détails erreurs · "
|
"c = copier le log · d = détails erreurs · "
|
||||||
"a = auto-refresh · r = rafraîchir · "
|
|
||||||
"Maj+glisser = sélectionner)\n"
|
"Maj+glisser = sélectionner)\n"
|
||||||
f" Log : {vm['log']}"
|
f" Log : {vm['log']}"
|
||||||
)
|
)
|
||||||
|
|
@ -732,10 +719,6 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
return disks, status, self._collect_tele(), errors
|
return disks, status, self._collect_tele(), errors
|
||||||
|
|
||||||
async def _tick_table(self):
|
async def _tick_table(self):
|
||||||
if self._auto_refresh:
|
|
||||||
await self._do_table()
|
|
||||||
|
|
||||||
async def _do_table(self):
|
|
||||||
# I/O (lectures de logs, stat disque, /proc) DÉPORTÉES en thread ->
|
# I/O (lectures de logs, stat disque, /proc) DÉPORTÉES en thread ->
|
||||||
# la boucle d'événements Textual reste fluide même sous forte
|
# la boucle d'événements Textual reste fluide même sous forte
|
||||||
# charge ou disque lent. Les mises à jour d'UI restent sur la boucle.
|
# charge ou disque lent. Les mises à jour d'UI restent sur la boucle.
|
||||||
|
|
@ -883,10 +866,6 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
async def _tick_log(self):
|
async def _tick_log(self):
|
||||||
if self._auto_refresh:
|
|
||||||
await self._do_log()
|
|
||||||
|
|
||||||
async def _do_log(self):
|
|
||||||
if not self._follow:
|
if not self._follow:
|
||||||
return
|
return
|
||||||
vm = self._vm_by_name(self._selected)
|
vm = self._vm_by_name(self._selected)
|
||||||
|
|
@ -905,10 +884,6 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
self._log.write(line)
|
self._log.write(line)
|
||||||
|
|
||||||
async def _tick_domstate(self):
|
async def _tick_domstate(self):
|
||||||
if self._auto_refresh:
|
|
||||||
await self._do_domstate()
|
|
||||||
|
|
||||||
async def _do_domstate(self):
|
|
||||||
# Relevé LENT (subprocess virsh) DÉPORTÉ en thread : ne bloque plus
|
# Relevé LENT (subprocess virsh) DÉPORTÉ en thread : ne bloque plus
|
||||||
# la boucle. Une VM absente de « virsh list » est EFFACÉE (« gone »).
|
# la boucle. Une VM absente de « virsh list » est EFFACÉE (« gone »).
|
||||||
try:
|
try:
|
||||||
|
|
@ -918,33 +893,6 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
for vm in vms:
|
for vm in vms:
|
||||||
self._domstate[vm["name"]] = states.get(vm["name"], "gone")
|
self._domstate[vm["name"]] = states.get(vm["name"], "gone")
|
||||||
|
|
||||||
# -- auto-refresh (a = on/off · r = rafraîchir une fois) ------------- #
|
|
||||||
def _update_autobar(self):
|
|
||||||
if self._auto_refresh:
|
|
||||||
msg = f" ▶ {t('Auto-refresh: ON')} (a = {t('turn off')})"
|
|
||||||
else:
|
|
||||||
msg = (
|
|
||||||
f" ⏸ {t('Auto-refresh: OFF')} "
|
|
||||||
f"(a = {t('turn on')} · r = {t('refresh once')})"
|
|
||||||
)
|
|
||||||
self.query_one("#autobar", Static).update(msg)
|
|
||||||
|
|
||||||
def action_toggle_auto(self) -> None:
|
|
||||||
self._auto_refresh = not self._auto_refresh
|
|
||||||
self._update_autobar()
|
|
||||||
if self._auto_refresh:
|
|
||||||
# Rafraîchit tout de suite en activant.
|
|
||||||
self.run_worker(self._do_domstate(), exclusive=False)
|
|
||||||
self.run_worker(self._do_table(), exclusive=False)
|
|
||||||
self.run_worker(self._do_log(), exclusive=False)
|
|
||||||
|
|
||||||
def action_refresh_now(self) -> None:
|
|
||||||
# Une seule actualisation, même auto-refresh coupé.
|
|
||||||
self.run_worker(self._do_domstate(), exclusive=False)
|
|
||||||
self.run_worker(self._do_table(), exclusive=False)
|
|
||||||
self.run_worker(self._do_log(), exclusive=False)
|
|
||||||
self.notify(t("Refreshed."))
|
|
||||||
|
|
||||||
# -- events --------------------------------------------------------- #
|
# -- events --------------------------------------------------------- #
|
||||||
def on_data_table_row_highlighted(self, event) -> None:
|
def on_data_table_row_highlighted(self, event) -> None:
|
||||||
name = event.row_key.value
|
name = event.row_key.value
|
||||||
|
|
@ -1136,10 +1084,8 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
await asyncio.to_thread(self._virsh_bulk, action, targets)
|
await asyncio.to_thread(self._virsh_bulk, action, targets)
|
||||||
verb = t("paused") if action == "suspend" else t("resumed")
|
verb = t("paused") if action == "suspend" else t("resumed")
|
||||||
self.notify(f"{len(targets)} VM {verb}.")
|
self.notify(f"{len(targets)} VM {verb}.")
|
||||||
# Rafraîchit tout de suite l'état libvirt (pause/reprise visible),
|
# Rafraîchit tout de suite l'état libvirt (pause/reprise visible).
|
||||||
# même si l'auto-refresh est coupé -> _do_domstate (forcé).
|
self.run_worker(self._tick_domstate(), exclusive=False)
|
||||||
self.run_worker(self._do_domstate(), exclusive=False)
|
|
||||||
self.run_worker(self._do_table(), exclusive=False)
|
|
||||||
|
|
||||||
app = Monitor()
|
app = Monitor()
|
||||||
if run_app:
|
if run_app:
|
||||||
|
|
|
||||||
|
|
@ -1625,7 +1625,6 @@ class TODO:
|
||||||
|
|
||||||
# 3) Application selon agrandir/réduire et l'état de la VM.
|
# 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 ?
|
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:
|
if shrink:
|
||||||
# DANGER : qcow2 --shrink ne réduit PAS le FS invité -> perte de
|
# DANGER : qcow2 --shrink ne réduit PAS le FS invité -> perte de
|
||||||
# données si le FS dépasse la cible. VM éteinte obligatoire.
|
# données si le FS dépasse la cible. VM éteinte obligatoire.
|
||||||
|
|
@ -1652,15 +1651,10 @@ class TODO:
|
||||||
):
|
):
|
||||||
print(t("Cancelled."))
|
print(t("Cancelled."))
|
||||||
return
|
return
|
||||||
# Réduction SÛRE via virt-resize (réduit FS + partition + GPT).
|
cmd = (
|
||||||
# On opère sur une COPIE : l'original n'est remplacé qu'en cas de
|
f"sudo qemu-img resize --shrink {shlex.quote(disk)} "
|
||||||
# SUCCÈS -> aucune corruption possible. (Le brut « qemu-img resize
|
f"{new_gb:g}G"
|
||||||
# --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":
|
elif state == "running":
|
||||||
# Agrandissement À CHAUD : le disque virtuel grossit, le FS invité
|
# Agrandissement À CHAUD : le disque virtuel grossit, le FS invité
|
||||||
# devra être étendu ensuite.
|
# devra être étendu ensuite.
|
||||||
|
|
@ -1671,125 +1665,30 @@ class TODO:
|
||||||
else:
|
else:
|
||||||
cmd = f"sudo qemu-img resize {shlex.quote(disk)} {new_gb:g}G"
|
cmd = f"sudo qemu-img resize {shlex.quote(disk)} {new_gb:g}G"
|
||||||
|
|
||||||
# 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("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 : 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}")
|
print(f"{t('Will execute:')} {cmd}")
|
||||||
os.system(cmd)
|
if self.execute.exec_command_live(cmd, source_erplibre=False) != 0:
|
||||||
return shutil.which("virt-resize") is not None
|
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.')}")
|
||||||
|
if self._is_yes(
|
||||||
|
input(t("Start the VM now? (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)
|
||||||
|
|
||||||
# Commande d'extension du FS racine (partition + FS) réutilisée par SSH
|
# Commande d'extension du FS racine (partition + FS) réutilisée par SSH
|
||||||
# et par le repli console série.
|
# et par le repli console série.
|
||||||
|
|
@ -2345,12 +2244,6 @@ class TODO:
|
||||||
"""Réponse affirmative, FR et EN (o/oui/y/yes)."""
|
"""Réponse affirmative, FR et EN (o/oui/y/yes)."""
|
||||||
return ans.strip().lower() in ("y", "yes", "o", "oui")
|
return ans.strip().lower() in ("y", "yes", "o", "oui")
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _is_yes_default_yes(ans):
|
|
||||||
"""Comme _is_yes mais le DÉFAUT (réponse vide) est OUI."""
|
|
||||||
a = ans.strip().lower()
|
|
||||||
return a == "" or a in ("y", "yes", "o", "oui")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _is_no(ans):
|
def _is_no(ans):
|
||||||
"""Réponse négative explicite, FR et EN (n/no/non). Utile pour les
|
"""Réponse négative explicite, FR et EN (n/no/non). Utile pour les
|
||||||
|
|
@ -2693,16 +2586,12 @@ class TODO:
|
||||||
return ssh_up
|
return ssh_up
|
||||||
|
|
||||||
# Paquets QEMU/libvirt pour le profil « Déploiement » (nos 3 gestionnaires).
|
# Paquets QEMU/libvirt pour le profil « Déploiement » (nos 3 gestionnaires).
|
||||||
# Paquets SYSTÈME du profil « ERPLibre Déploiement » : QEMU/libvirt/
|
|
||||||
# virtinst + libguestfs (virt-resize/virt-filesystems, requis pour la
|
|
||||||
# RÉDUCTION SÛRE de disque). Ce sont des binaires système (OCaml) : ils ne
|
|
||||||
# peuvent PAS vivre dans .venv.erplibre (venv Python) — d'où leur place ici.
|
|
||||||
_QEMU_QEMU_PKGS = (
|
_QEMU_QEMU_PKGS = (
|
||||||
"(sudo apt-get install -y qemu-kvm libvirt-daemon-system virtinst "
|
"(sudo apt-get install -y qemu-kvm libvirt-daemon-system virtinst "
|
||||||
"cloud-image-utils libguestfs-tools 2>/dev/null || sudo dnf install "
|
"cloud-image-utils 2>/dev/null || sudo dnf install -y qemu-kvm "
|
||||||
"-y qemu-kvm libvirt virt-install cloud-utils guestfs-tools "
|
"libvirt virt-install cloud-utils 2>/dev/null || sudo pacman -S "
|
||||||
"2>/dev/null || sudo pacman -S --needed --noconfirm qemu-desktop "
|
"--needed --noconfirm qemu-desktop libvirt virt-install 2>/dev/null "
|
||||||
"libvirt virt-install libguestfs 2>/dev/null || true)"
|
"|| true)"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _qemu_pick_install_profile(self):
|
def _qemu_pick_install_profile(self):
|
||||||
|
|
@ -3254,7 +3143,7 @@ class TODO:
|
||||||
if self._is_yes(ans):
|
if self._is_yes(ans):
|
||||||
install_branch = self._qemu_pick_branch()
|
install_branch = self._qemu_pick_branch()
|
||||||
_label, install_cmd = self._qemu_pick_install_profile()
|
_label, install_cmd = self._qemu_pick_install_profile()
|
||||||
install_monitor = self._is_yes_default_yes(
|
install_monitor = self._is_yes(
|
||||||
input(t("Interactive monitoring dashboard? (y/N): "))
|
input(t("Interactive monitoring dashboard? (y/N): "))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1424,34 +1424,6 @@ TRANSLATIONS = {
|
||||||
"fr": "Impossible de lire la taille actuelle du disque ; abandon.",
|
"fr": "Impossible de lire la taille actuelle du disque ; abandon.",
|
||||||
"en": "Could not read current disk size; aborting.",
|
"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": {
|
"click to expand": {
|
||||||
"fr": "cliquer pour déplier",
|
"fr": "cliquer pour déplier",
|
||||||
"en": "click to expand",
|
"en": "click to expand",
|
||||||
|
|
@ -1460,30 +1432,6 @@ TRANSLATIONS = {
|
||||||
"fr": "Aucune erreur détectée.",
|
"fr": "Aucune erreur détectée.",
|
||||||
"en": "No error detected.",
|
"en": "No error detected.",
|
||||||
},
|
},
|
||||||
"Auto-refresh: ON": {
|
|
||||||
"fr": "Rafraîchissement auto : ACTIVÉ",
|
|
||||||
"en": "Auto-refresh: ON",
|
|
||||||
},
|
|
||||||
"Auto-refresh: OFF": {
|
|
||||||
"fr": "Rafraîchissement auto : COUPÉ",
|
|
||||||
"en": "Auto-refresh: OFF",
|
|
||||||
},
|
|
||||||
"turn off": {
|
|
||||||
"fr": "couper",
|
|
||||||
"en": "turn off",
|
|
||||||
},
|
|
||||||
"turn on": {
|
|
||||||
"fr": "activer",
|
|
||||||
"en": "turn on",
|
|
||||||
},
|
|
||||||
"refresh once": {
|
|
||||||
"fr": "rafraîchir",
|
|
||||||
"en": "refresh once",
|
|
||||||
},
|
|
||||||
"Refreshed.": {
|
|
||||||
"fr": "Rafraîchi.",
|
|
||||||
"en": "Refreshed.",
|
|
||||||
},
|
|
||||||
"Test a VM (open Odoo in a CLI browser)": {
|
"Test a VM (open Odoo in a CLI browser)": {
|
||||||
"fr": "🧪 Tester une VM (ouvrir Odoo dans un navigateur CLI)",
|
"fr": "🧪 Tester une VM (ouvrir Odoo dans un navigateur CLI)",
|
||||||
"en": "🧪 Test a VM (open Odoo in a CLI browser)",
|
"en": "🧪 Test a VM (open Odoo in a CLI browser)",
|
||||||
|
|
@ -2126,8 +2074,8 @@ TRANSLATIONS = {
|
||||||
"en": "ERPLibre Deployment (+ QEMU + dev)",
|
"en": "ERPLibre Deployment (+ QEMU + dev)",
|
||||||
},
|
},
|
||||||
"Interactive monitoring dashboard? (y/N): ": {
|
"Interactive monitoring dashboard? (y/N): ": {
|
||||||
"fr": "Suivi interactif (dashboard) ? (O/n, défaut : oui) : ",
|
"fr": "Suivi interactif (dashboard) ? (o/N, défaut : non) : ",
|
||||||
"en": "Interactive monitoring dashboard? (Y/n, default: yes): ",
|
"en": "Interactive monitoring dashboard? (y/N, default: no): ",
|
||||||
},
|
},
|
||||||
"resolving IP...": {
|
"resolving IP...": {
|
||||||
"fr": "résolution de l'IP...",
|
"fr": "résolution de l'IP...",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue