Every reachable machine ended up in virt-manager, not just the ones hosting
VMs. The probe ran « for n in $(sudo virsh list --all --name 2>/dev/null) »:
with no virsh, the command substitution is empty, the loop body never runs and
the snippet exits 0 with no output — indistinguishable from « QEMU is here,
it just has no VM ». Both read as a libvirt host.
The probe now states it outright, « LIBVIRT<TAB>yes|no » as its first line, so
the two cases separate: a machine WITHOUT QEMU is skipped, a machine with QEMU
and no VM is still offered — that is where one would create some.
Verified against the real snippet: this host answers « yes » plus its two VMs,
and with virsh out of PATH it answers « no ». Then on a simulated fleet, only
vm-avec-qemu and vm-qemu-sans-vm are proposed; the plain dev VM and the
unreachable one are not.
Also: the ~/.ssh/config blocks were missing IdentityFile. Every entry now
names the private key it needs — the one cloud-init injected, or the one just
deployed — with IdentitiesOnly yes beside it. Without that flag IdentityFile
ADDS to the agent's identities instead of replacing them, and a slightly full
agent hits « Too many authentication failures » before reaching the right key.
The .pub suffix is stripped: IdentityFile wants the private half.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Writing ~/.ssh/config only helps if the key is accepted at the other end.
Both SSH options now offer to create an ed25519 key when none exists — the
same choice deploy_qemu.ensure_ssh_key makes, so created and adopted VMs share
one key — and to push it with ssh-copy-id.
Hosts that already accept the key are skipped, tested with
PasswordAuthentication=no: without it ssh would fall back to the password and
every host would look like it already had the key. ssh-copy-id runs on the
real terminal rather than through captured output, otherwise its password
prompt would be invisible.
In the recursive walk the key is deployed BEFORE probing each level, not at
the end. The probe uses BatchMode, so an un-keyed machine answers nothing and
the level below it stays invisible — deploying afterwards would find only the
first level.
virt-manager, when installed, gets the machines that actually run libvirt
added to its connection list, so their nested VMs can be driven from the local
GUI. The URI uses the SSH ALIAS rather than the raw IP: qemu+ssh goes through
the ssh binary, hence ~/.ssh/config, so the alias already carries both the
address and the ProxyJump — a bare IP could not reach a nested VM at all.
Connections live in GSettings, not a file. The list is READ first and written
back merged, so nothing already configured is lost, and a failed read (no
schema, no virt-manager) simply means the whole feature stays silent — no
prompt, no write. virt-manager rewrites its settings when it exits, so a
warning says to restart it.
Verified: key generated with 0600 on the private half and reused on the second
call; ssh-copy-id issued only for hosts that need it; the recursive walk
deploying level by level before each probe; GSettings merge keeping existing
URIs and skipping the write when nothing is missing; « @as [] », a populated
list and a missing schema all parsed correctly.
Caveat: virt-manager is not installed on this machine, so the absent path was
exercised for real and the write path only against a stubbed gsettings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
~/.ssh/config entries were only ever written while deploying a VM. A fleet
that already exists — or one whose DHCP leases have moved — had no way to
refresh them. Entry [13] of the QEMU menu does it on demand:
[1] update ~/.ssh/config for the local VMs
[2] add the nested VMs through ProxyJump, recursively
The second one matters because of the « ERPLibre Deployment (+ QEMU + dev) »
profile: a VM built that way hosts VMs of its own, on its own private network.
Those are not reachable from this host at all — only from their parent. So
each level is written with a ProxyJump to the level above, and OpenSSH chains
the hops on its own. « ssh erplibre-fedora-42 » then works from here even
though the address only means something two machines away.
The recursion probes over « ssh <alias> », i.e. through the block just
written, so the parent's own ProxyJump applies automatically and one probe
works identically at any depth. One SSH connection per MACHINE, not per VM: a
single snippet returns every « name<TAB>ip » pair, falling back to the guest
agent when the dnsmasq lease is missing. Passwordless sudo is a given here —
the cloud-init config grants it (deploy_qemu.py:1175).
A nested VM keeps its short name, which is what one wants to type, and is only
prefixed with its parent on collision — so discovering a machine that already
exists elsewhere never overwrites the other one's entry. Depth defaults to 2
(host, VM, nested VM) and already-seen aliases are skipped, which is what
stops a cycle: a child that reports its own grandparent.
Verified on a simulated two-level fleet including a deliberate cycle: the
ProxyJump chain is correct at each level, the colliding name is prefixed, the
parent block is not overwritten, and only one probe per machine is issued. The
remote snippet itself was run for real on this host — valid POSIX sh, two VMs
with their addresses. A VM without an IP is skipped rather than written with
an empty HostName.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Uninstalling before the 13->14 bump asked for 15 modules; 12 of them no longer
exist in the 13.0 addons path. check_addons_exist.py rejects the command on
the first missing name, so the uninstall aborted whole — and the 3 modules
that WERE there stayed installed, blocking the bump for a reason that had
nothing to do with them.
The list is now split before the script is called, using check_addons_exist
which was already there and simply never consulted at this point. « Missing »
means the addons path has no code for it, not that the database lacks it —
the distinction is the whole point, since Odoo cannot uninstall a module whose
code is gone.
When some are missing, a choice is offered rather than a failure:
[1] uninstall the present ones, skip the missing (default)
[2] try the whole list anyway (it will fail)
[3] uninstall nothing, continue
Only what is really uninstalled is subtracted from the per-version module
list, so a module left in place stays counted as installed — which it is. The
missing names are written to the progression comments, so what was skipped is
still on record afterwards.
Verified on the exact list from the failure: 3 present and 12 missing,
option 1 issuing a command with only the 3 and leaving the 12 counted as
installed, option 2 sending all 15, option 3 running nothing.
Also swapped entries 4 and 5 of the Deploy menu — QEMU/KVM now sits at [4],
NTFY at [5].
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The migration now opens with the same choice the QEMU deployment does — TUI
or line-by-line prompts — settled in advance by TODO > Configuration if you
want it to be. Choosing the TUI loads the saved progression and asks the very
same first question: where does this migration stand, and where do we resume?
prompt_resume was one function that rendered, read and decided at once, so a
second view would have meant a second copy of the decision. It is now three:
resume_context() the progression -> plain data (file, database, target,
steps with their icon and detail, version bumps)
print_resume() renders it on the terminal
apply_resume_answer() answer -> (progression, changed)
The TUI returns the SAME answer strings as the prompt — c, n, r, q, 0..4,
4.<version> — so apply_resume_answer stays the only place that decides what a
choice means. Neither view can drift into describing the migration
differently, because both render the same context.
In the TUI the steps are a table and the version bumps a list: Enter on either
replays from there, which is what « [0-4] » and « [4.N] » meant in text. The
cursor opens on the first unfinished step and on the first unmigrated version
— where it stopped is where one usually wants to act.
Both views gain « q », quit without doing anything. A TUI needs Escape to do
something sane, and an escape hatch present in only one of the two views is
exactly the kind of divergence this split exists to prevent. execute_odoo_
upgrade returns immediately on it, writing nothing.
Verified on a 12->18 progression with steps 0-3 done and 2 of 6 bumps
migrated: identical context feeding both views, Enter on step 2 giving « 2 »,
Enter on the 15 bump giving « 4.15 », the c/n/r/q/Escape shortcuts, and every
answer producing the same progression through both paths — « 2 » leaving only
the state of steps 0 and 1, « 4.15 » resetting the clone list from the third
bump on so the half-migrated intermediate database gets rebuilt. « q » checked
to leave the progression file byte-identical.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deploying a VM meant answering a dozen questions in a row, never seeing the
whole of one's choices, and starting over to revisit an answer. The first
question is now which interface to use — TUI form or the classic prompts —
defaulting to whatever TODO > Configuration says.
The form shows every setting at once with a plan that recomputes on each
change: names, resources, and the two collisions marked as they arise (a
defined VM is skipped, an orphan qcow2 will make deploy_qemu fail). F2 edits
one VM, F3 previews the commands, F5 deploys, F6/F7/F8 select all / main
versions / none.
Function keys rather than ctrl+letter: ctrl+p is Textual's command palette and
silently swallowed the shortcut, and a bare letter is eaten by whichever input
has focus.
Both interfaces go through _qemu_deploy_parts_for, so the same choices produce
the same command by construction — and a test now drives the CLI prompts and
the form to the same state and compares the argv, which is what keeps them
from drifting.
Nothing privileged or networked runs inside the form. Every virsh call in this
codebase goes through sudo, and a password prompt while Textual owns the
terminal would wreck the display; the domain list and the remote branches are
fetched before the app starts and arrive as plain data.
The progress view is optional (preference: CLI output stays the default, since
it is the easiest to copy from). It gives one collapsible block per VM,
expanded while running, folded on success — and left OPEN on failure, which is
the part worth reading. « c » copies the selected log, « C » all of them.
On copying from a TUI over SSH: copy_to_clipboard emits OSC 52, which the
LOCAL terminal emulator interprets, so it does reach the workstation's
clipboard. Two caveats are handled: the payload is capped at 100 kB keeping
the TAIL (some terminals truncate long OSC 52), and the notification says a
compatible terminal is required — macOS Terminal.app has none, tmux needs
set-clipboard on.
Verified: pure logic (profiles, per-VM overrides surviving a profile change,
totals, statuses) by direct calls; the form headless via run_test — default
selection, F6/F7/F8, profile switch, and the orphan guard demanding a second
F5; parity CLI/TUI on identical argv; the progress view on three fake jobs,
with the failure staying open and the clipboard filled. The semaphore bounding
concurrency was measured: four 0.35 s jobs take 1.63 s at 1 and 0.58 s at 4 —
without it, « 4 in parallel » launched every VM at once.
Also removed a duplicated @staticmethod on _qemu_install_dir, harmless since
Python 3.10 but misleading.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_qemu_deploy was ~400 lines mixing a dozen prompts with the parallel
deployment, IP resolution, ~/.ssh/config and the ERPLibre install. A second
interface could not be added to it without duplicating all of that — and the
repository already shows where duplication leads: _qemu_choose_cli_browser
(todo.py) and Monitor._choose_browser (qemu_install_monitor.py) are the same
function twice, and they have already drifted apart.
The function now has three parts around a plain dict, the SPEC:
_qemu_collect_vms_cli arch, catalog, resources, names -> the VM list
_qemu_collect_options_cli SSH key, install, parallelism -> the spec
_qemu_run_spec consumes a spec, asks nothing
_qemu_deploy_parts_for is the single point every command goes through, so two
interfaces producing the same spec necessarily produce the same command —
which makes their divergence testable rather than a matter of discipline.
Pure, I/O-free helpers come out of the body: _qemu_catalog_entries (the flat
distro × version × arch list), _qemu_arches_for, _qemu_make_vm,
_qemu_split_existing and _qemu_orphan_disks. The last two matter beyond
tidiness — the form must recompute collisions on every keystroke, and every
virsh call in this file goes through sudo. Existence is now resolved with ONE
virsh list --all --name (_qemu_list_domains, already present) instead of one
sudo per VM, and the orphan-disk scan needs no privileges at all.
VMs travel as dicts rather than 6-tuples plus a parallel names list. The
tuple-based resource prompts are left untouched and converted at the boundary.
No behaviour change intended, and verified as such: replaying identical
scripted answers against a worktree pinned at the previous commit produces
byte-identical output — including the granular selection spanning three
architectures — once the repository path and the instantaneous free-RAM
reading are normalised.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nothing in the CLI could be remembered from one session to the next except the
language, which lives in env_var.sh with its own ad-hoc parser. Entry [6]
Configuration, below Telemetry, now groups the settings that belong to the
USER rather than to the repository.
todo_prefs.py stores them in ~/.erplibre/todo_prefs.json — the same place and
the same best-effort shape as todo_telemetry.py, since both are per-user and
per-machine, and neither must ever prevent the CLI from starting. A DEFAULTS
table gives every known key its fallback, so a missing or corrupt file simply
reads as defaults.
The two keys added here prepare the QEMU deploy form: which interface to use
(ask / TUI / classic) and what to display while deploying (CLI output or TUI).
They are declared once in _PREF_CHOICES — the screen, the current-value label
and the editor all derive from that single table, so adding a preference is
one entry, not three edits.
Language keeps its own mechanism: it is read before the preferences file
exists and is consumed by shell scripts too.
Verified against a temporary HOME: defaults returned with no file on disk, a
change persisted and reflected in the menu, and reset falling back to
defaults.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resources per VM offered x1..x4 only — anything else meant deploying with the
wrong sizing and resizing afterwards. « [5] Custom » now asks vCPU, RAM and
disk once for the whole fleet, and vCPU joins disk and RAM in the per-VM
customisation. Presets 1, 2, 4, 6, 8, 16, 24, 32.
The two places ask the SAME three questions, so they are one definition each
(_qemu_ask_disk / _qemu_ask_ram / _qemu_ask_cpu) instead of two copies that
would drift. vCPU is no longer a single global value: it travels per VM
through the whole chain, down to --vcpus.
Overcommit is allowed on an explicit choice — KVM permits more vCPU than
cores — and only warned about. The x1..x4 path still caps at the host core
count: that one is an automatic computation, not a decision.
Three prompts default to yes (install ERPLibre, ~/.ssh/config, deploy): they
are what one wants nearly every time, and typing « o » on each was noise.
Flipping a destructive-by-omission default demanded the guards below.
Before deploying, a final review lists everything that will change — VMs to
create with their effective disk (ERPLibre's +5 G included), VMs left
untouched, install profile and branch, SSH key, ~/.ssh/config, parallelism.
Answering no asks for confirmation rather than dropping every answer given
over a dozen prompts.
Name collisions are now reported BEFORE the wait, with their two very
different consequences: an already-defined VM is skipped and nothing is
overwritten, while a qcow2 left behind by a deleted VM makes deploy_qemu fail,
since it refuses to overwrite without --force. Continuing requires an explicit
yes; the default is no.
Also translated two strings of this flow that had no entry and stayed in
English (« Resolving VM IPs… », « no IP »).
Verified by driving the prompts with scripted answers: custom profile applied
and left intact when blank, per-VM override, 32 vCPU on 28 cores warned but
accepted, review with and without ERPLibre install, no → no → deploy, no →
yes → cancel, and collisions defaulting to no. Rendering checked in fr and en.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The install history was only surfaced as a « ~5m avg (3) » suffix next to a
distro. Entry [12] now shows what that history actually contains: totals and
success rate, period covered, median/min/max and cumulated duration, then a
breakdown by distribution, version and architecture, plus the current VMs and
the disk they occupy. « [r] » erases the history after confirmation.
record_duration() was only called on success, so no success rate could ever be
computed. Failures are now recorded with ok=False. They are counted separately
and EXCLUDED from the averages and the ETA: how long a failed install ran says
nothing about how long a successful one takes. Entries written before the flag
existed have no « ok » key and are read as successes, which they were.
Aggregation lives in qemu_install_monitor.py as pure functions (stats_summary,
stats_by, all_runs, reset_stats), display in todo.py — the split the file
already follows.
Disk presets extended to 400G, 600G, 800G, 1T, 1.5T and 2T. The parser only
understood G, so « 1T » would have been rejected: sizes are now normalised
through _qemu_parse_disk (1 T = 1024 G, decimal comma accepted), since the rest
of the chain reasons in gigabytes.
Verified with a synthetic history of 9 runs including 2 failures: the rate,
the per-group failure counts and the reset all behave; a group with no success
shows « — » rather than a misleading « ~0s ».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The presets stopped at 32768 MB; current virtualization hosts go well beyond
that. The series now doubles up to 262144 MB (256G).
The prompt is in MB while people think in GB, so each preset carries its
equivalent: « [g] 65536 (64G) ». With nine of them the line no longer fits, so
suggestions are laid out five per row. Disk presets keep the plain format.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both prompts expected a raw number, so common values had to be retyped every
time. They now list suggestions:
[a] 20G [b] 40G [c] 60G [d] 80G [e] 120G [f] 200G
New disk size in G, blank = keep (20G):
Letters start at « a » so they can never collide with a value typed directly:
anything starting with a digit is read as the value itself, and blank still
keeps the current one. A letter outside the range is rejected instead of being
silently taken for a size.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On a rolling distro every fresh development VM was born unable to create VMs.
« make install_os » runs a full system upgrade, the kernel package is replaced
and /lib/modules/<running kernel> disappears. modprobe bridge then fails and
libvirt cannot create virbr0, so the « default » network stays inactive and
virt-install dies on « network 'default' is not active » -- with every package
correctly installed. Measured twice on a freshly created Arch VM: booted on
7.1.3-arch1-3 at 05:44, upgraded to 7.1.5.arch1-2 at 05:46.
Only a reboot fixes it, and one is enough: the default network is already
flagged autostart, so libvirt brings it up by itself once the modules match.
--setup-host therefore gains --reboot-if-needed, used by the deployment
profile. The reboot is scheduled through systemd-run --on-active=5 rather than
issued immediately, otherwise it would kill the installer's SSH session and the
orchestrator would report a failure for a VM that actually succeeded. Without
the flag the behaviour is unchanged: explain and exit 1, which is what a
workstation wants.
Also fix « Error setting up logfile: No write access to
/var/tmp/erplibre-virtinst/virt-manager »: that path was shared, so a first run
under sudo created it as root and later non-root runs could not write. It is
now per-UID.
Verified on the Arch VM that failed: without the flag it exits 1 with the
diagnosis; with it, the reboot is scheduled and the command still returns 0;
after the reboot the kernel and modules match, « default » is active on its own
and virbr0 exists. The cache directory is created as erplibre:erplibre 0700.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The « ERPLibre Deployment (+ QEMU + dev) » profile installed packages with a
one-liner ending in « || true » and hiding stderr, so a broken host looked
installed. Reproduced on erplibre-arch-latest: every binary was present, yet
virt-install failed and « virsh list --all » could not reach any hypervisor.
Three distinct causes, all unhandled:
- The user was never added to the libvirt group. Without it a non-root libvirt
client falls back to qemu:///session, where the « default » network does not
exist, so « --network network=default » fails while everything looks
installed. Being in the group grants the RIGHT to reach qemu:///system but
does NOT change the default URI, so virt-install and virsh now pass
« --connect qemu:///system » explicitly (new LIBVIRT_URI constant).
- dnsmasq was missing: ensure_tools only installed DAEMON_PACKAGES when the
daemon was absent, and libvirt was already there. setup_host now forces them.
- On a rolling distro, « make install_os » upgrades the kernel and the package
manager removes /lib/modules/<running kernel>. modprobe bridge then fails and
libvirt cannot create virbr0 (« Unable to create bridge virbr0: Package not
installed »). No package fixes that, only a reboot: it is now diagnosed and
reported instead of surfacing as an unreadable virsh error.
The profile now calls « deploy_qemu.py --setup-host », which reuses the
existing ensure_* chain, so package names stay defined in one place
(TOOL_PACKAGES / DAEMON_PACKAGES) and keep working for apt, dnf, pacman,
zypper and brew. It fails loudly instead of « || true ».
Verified on the Arch VM that failed: setup-host reports the stale kernel and
exits 1; after a reboot it installs dnsmasq, joins libvirt/kvm, starts the
default network, and reports « Hôte prêt » (exit 0, idempotent on rerun).
The generated command now reads « virt-install --connect qemu:///system ».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Measured on erplibre-ubuntu-2404: unattended-upgrades fired in the middle of
an Odoo 12->13 migration and restarted the PostgreSQL cluster three times
(« received fast shutdown request »). OpenUpgrade lost its connection and the
intermediate database was left half migrated.
On a development VM the ERPLibre installer now turns off unattended-upgrades
and the apt-daily timers, and drops an apt.conf.d snippet so they stay off
across reboots. dnf-automatic gets the same treatment on Fedora. It runs right
after the cloud-init wait and before the apt-get calls, so apt-daily can no
longer grab the lock between the two either -- the same contention that made
« apt-get update » fail during deployment.
Production VMs are left untouched: automatic security updates must stay on
there. The switch is the existing dev/prod answer, already threaded down to
_qemu_erplibre_remote_cmd.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le montage sshfs de « Configurer sshfs » ajoute désormais
« -o follow_symlinks » : sshfs résout les symlinks côté serveur. Sans ça,
git échoue sur les worktrees google-repo d'ERPLibre (leur .git est une
chaîne de symlinks relatifs profonds vers .repo/projects et project-objects)
-> « erreur à la lecture de .git », git status/commit impossibles sur le
montage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
L'entrée « Update - Update all developed staging source code » quitte le
menu Execute (renumérotation 9->8 … 15->14, dispatch elif mis à jour) et
rejoint le sous-menu « Code - Outil pour développeur » (dernière entrée,
appelle prompt_execute_update). Le libellé/i18n (🔃) est réutilisé.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
« Renommer quelles VM ? » devient « Modifier quelles VM ? » : pour chaque
VM choisie, on peut changer le NOM, la taille de DISQUE et la RAM (vide =
garder). _qemu_customize_names -> _qemu_customize_vms renvoie (names,
selected) avec les valeurs mises à jour.
Le multiplicateur de ressources est désormais CUIT dans `selected` (RAM
finale) et le nombre de vCPU fixé une fois (uniforme) -> un override de RAM
par VM est une valeur ABSOLUE, sans ambiguïté avec le multiplicateur. La
fonction eff_res disparaît (plan, dry-run et déploiement lisent les valeurs
finales de `selected`).
Validé : modif VM 1 (nom/disque/RAM) appliquée, VM 2 inchangée.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
« SELinuxContext=unconfined_u:unconfined_r:unconfined_t:s0 » ne débloque PAS
l'exécution : la transition init_t -> unconfined_t est refusée par la
politique -> le service échouait toujours en 203/EXEC (« Permission denied »
sur run.sh, contexte user_home_t) sur Fedora.
En DEV (VM jetable, « SELinux relâché ») on passe désormais SELinux en
PERMISSIF (setenforce 0 + persistance dans /etc/selinux/config) si actif ->
le service exécute run.sh/venv sous /home sans blocage.
PROD reste confiné (/opt/erplibre hors user_home_t + restorecon) — à
peaufiner quand on reviendra sur la prod.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le fix précédent (DPkg::Lock::Timeout) ne suffisait pas : cette option NE
couvre PAS le verrou /var/lib/apt/lists/lock de « apt-get update ». Au 1er
boot, cloud-init/apt-daily tient ce verrou -> update échouait AUSSITÔT
-> lists vides -> « Unable to locate package git » (exit 100), toujours sur
debian-12.
On RÉESSAIE désormais « apt-get update » jusqu'à libération du verrou (et
lists peuplées), borné à ~5 min (30×10s), avant l'install. Une fois update
OK, git/make s'installent normalement.
Note : le remote_cmd est construit côté HÔTE -> relancer le CLI todo pour
que la nouvelle commande soit envoyée aux VM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Au déploiement (« Déployer une ou plusieurs VM »), après le choix de la
branche, on demande l'ENVIRONNEMENT cible (dev par défaut / prod).
- DEV : ERPLibre dans ~/git/erplibre ; service systemd unconfined si
SELinux actif (comportement inchangé).
- PROD : ERPLibre dans /opt/erplibre (sudo git clone + chown à
l'utilisateur) ; service systemd CONFINÉ par SELinux (pas d'unconfined)
+ restorecon des contextes -> hors user_home_t, un service peut exécuter
le contenu sans lever le confinement.
Le drapeau `prod` est propagé : _qemu_deploy -> _qemu_install_erplibre_
monitored/_vm -> _qemu_erplibre_remote_cmd -> _qemu_odoo_service_cmd.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Corrections issues de la revue complète des 13 VM (6 en échec réel) :
- APT lock (debian-12) : au 1er boot, cloud-init/apt-daily tient le verrou
et « apt-get install » échouait aussitôt. Ajout de
« -o DPkg::Lock::Timeout=600 » (update + install) -> attend le verrou.
- wkhtmltopdf Debian 13 « trixie » (debian-13) : on prenait le build
« bullseye » (dépend de libssl1.1, absent de trixie) -> gdebi échouait.
Désormais bookworm pour bookworm/trixie/+. ET l'échec gdebi est NON
bloquant (avertissement au lieu d'exit 1 : wkhtmltopdf est optionnel,
sinon tout install_os avortait et Odoo n'était jamais installé).
- SELinux 203/EXEC (fedora-41/43/44) : un service système ne peut pas
exécuter run.sh/venv sous /home (contexte user_home_t). Ajout
conditionnel de « SELinuxContext=unconfined_u:unconfined_r:unconfined_t:s0 »
au service (uniquement si getenforce != Disabled).
- poetry status 1 (ubuntu-2604) : « -q » masquait la cause. À l'échec, on
rejoue « poetry install -v » pour capturer l'erreur dans le log.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
À l'agrandissement, on indique désormais l'espace libre de l'hôte et la
taille virtuelle MAX « soutenable » ≈ (taille réelle actuelle + libre
hôte) — affichée AVANT la saisie pour guider le choix. Si la cible la
dépasse, avertissement NON bloquant : le qcow2 est creux, donc OK tant que
la VM ne remplit pas, mais au-delà l'hôte tomberait à court d'espace.
Ex. : réel 115G + libre 30G -> max soutenable ~145G ; viser 190G affiche
« dépasse de ~45G — surallocation ». L'opération n'est pas bloquée.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sur un hôte en français, « virsh domstate » renvoie « en cours d'exécution »
au lieu de « running ». Le test « state == "running" » échouait donc :
l'agrandissement à chaud tombait dans la branche « qemu-img resize » (au
lieu de « virsh blockresize ») sur une VM ALLUMÉE -> « Failed to get write
lock ».
On force désormais LC_ALL=C / LANG=C (via _qemu_c_env) sur tous les outils
dont on PARSE la sortie, pour avoir l'anglais quelle que soit la locale :
virsh domstate/domname/dominfo/domblklist/list, sgdisk -i, dumpe2fs,
resize2fs -P (todo.py) et virsh list --all (dashboard). Cela répare aussi,
en locale fr, la détection d'état (pause/éteinte), l'analyse des infos
avancées (CPU/RAM) et le parsing de la réduction (partition/GPT).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Trois améliorations autour de la sauvegarde de disque à la réduction :
- La sauvegarde .bak n'est plus systématique : on DEMANDE (défaut OUI)
« Sauvegarder le disque avant réduction ? ». Si non, avertissement (un
échec pourrait casser le disque, pas de restauration possible).
- À la FIN, après avoir proposé de démarrer la VM (donc après test manuel
possible), on propose d'EFFACER la sauvegarde (défaut NON -> on la garde
par prudence).
- « Nettoyer QEMU » détecte désormais les sauvegardes *.qcow2.bak (libellé
« sauvegarde de disque (redim.) ») et permet de les effacer.
_qemu_shrink_revert gère l'absence de sauvegarde (avertit de lancer fsck
au lieu de restaurer). Validé de bout en bout sur image jetable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
« Partition à réduire introuvable » sur une vraie VM : _qemu_root_part
parsait lsblk en positionnel et EXIGEAIT 4 colonnes. Juste après le connect
nbd, le FSTYPE n'est pas encore en cache -> colonne VIDE -> 3 tokens ->
toutes les partitions étaient ignorées. (Le test initial passait car le FS
avait eu le temps d'être détecté.)
- lsblk -P (paires clé="valeur") : robuste aux colonnes vides ; on repère
la partition par TYPE="part" et on sonde le FSTYPE via blkid si absent.
- _qemu_nbd_connect attend l'APPARITION des sous-périphériques nbdNpM
(jusqu'à ~15 s) avant de rendre la main.
- partprobe silencieux (capture) -> plus de spam « Invalid argument during
seek » pendant la réparation GPT.
Validé sur image jetable au layout cloud (p1 root + p14 bios + p15 ESP),
détection immédiate après connect : 25G -> 15G, GPT « No problems found »,
partitions préservées, fsck propre.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
La réduction « sûre » via virt-resize ne marchait pas : (1) j'utilisais
« virt-filesystems -b » (option INEXISTANTE -> aucune partition détectée ->
« abandon »), et (2) libguestfs est inutilisable ici (aucun vmlinuz dans
/boot -> l'appliance supermin échoue). La VM restait donc à 25G.
Réécriture SANS libguestfs, avec des outils de base présents et éprouvés
(qemu-nbd, e2fsck, resize2fs, sgdisk, parted/partprobe) :
1. copie .bak AVANT toute modification ;
2. nbd + détection de la racine (plus grosse partition, ext seulement) ;
3. e2fsck -> resize2fs (FS) -> sgdisk réécrit la partition en PRÉSERVANT
type/UUID/nom (PARTUUID intact) -> qemu-img --shrink (conteneur) ->
sgdisk -e (GPT de secours) -> fsck final ;
4. en cas d'échec à N'IMPORTE quelle étape : restauration depuis .bak
-> corruption impossible.
Validé de bout en bout sur une image jetable (GPT + ext4, racine à offset
élevé, 800M de données) : 25G -> 15G, GPT « No problems found », fsck
propre, UUID préservé, données intactes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Si le dashboard se ferme (bug, sortie), on peut désormais le ROUVRIR sur un
run passé pour reprendre l'analyse. Nouvelle entrée [11] du menu QEMU :
« 📈 Rouvrir le suivi d'installation (dernier run / historique) ».
- mon.list_install_runs() : liste les runs (~/.erplibre/qemu-install/*/
session.json) triés du plus récent au plus ancien.
- _qemu_reopen_monitor : affiche l'historique (VM par run), choix (défaut =
le dernier), puis run_monitor(session.json) rouvre le dashboard.
- « Lister les images » passe de [11] à [12] ; config -> 13+.
Validé : 20 runs listés, dashboard reconstruit (titre + 5 VM) depuis un
session.json passé.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
« Suivi interactif (dashboard) ? » répondait NON par défaut (réponse vide).
Le défaut est désormais OUI : nouveau helper _is_yes_default_yes (vide = oui)
et libellé « (O/n, défaut : oui) » / « (Y/n, default: yes) ».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
virt-resize/virt-filesystems (requis pour la réduction SÛRE de disque) sont
des binaires SYSTÈME (OCaml), pas des paquets Python : ils ne peuvent pas
vivre dans .venv.erplibre. On les ajoute donc au jeu de paquets système du
profil « ERPLibre Déploiement » (à côté de qemu/libvirt/virtinst) :
apt libguestfs-tools · dnf guestfs-tools · pacman libguestfs.
Ainsi tout hôte provisionné avec ce profil peut réduire un disque sans
risque. (La commande de redimensionnement propose déjà d'installer
libguestfs à la demande si absent.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Après un shrink, « Démarrer la VM » faisait « virsh start <id> » : une fois
la VM éteinte, l'ID numérique disparaît -> « failed to get domain 32 ».
On résout désormais le NOM canonique dès le début de _qemu_resize_disk
(VM encore allumée, ID résoluble) et on l'utilise partout, y compris pour
le redémarrage final. Plus de dépendance à l'ID une fois la VM arrêtée.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le choix du navigateur propose désormais « [i] Installer un autre
navigateur » en plus des navigateurs déjà installés. L'option ouvre le
sous-menu d'installation (choix w3m/lynx/links/elinks, commande adaptée à
l'OS, validation), même s'il existe déjà un navigateur.
Le flux d'installation est extrait dans _qemu_install_cli_browser
(réutilisé quand aucun navigateur n'est présent ET via l'option [i]).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le navigateur ne faisait qu'imprimer sans réagir au clavier : la commande
passait par exec_command_live, qui exécute avec stdout=PIPE (et sans stdin
terminal) -> un navigateur texte (w3m/elinks) n'a pas de TTY interactif.
On lance désormais le navigateur avec os.system(), qui hérite du vrai
terminal (stdin/stdout/stderr) — même principe que le suivi d'installation
(TUI) qui l'appelle dans self.suspend(). Ici, en CLI simple, aucun suspend
n'est nécessaire.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nouvelle entrée [10] dans « Gérer » du menu QEMU/KVM : 🧪 Tester une VM.
Elle liste les VM, demande laquelle, résout son IP puis ouvre
http://IP:8069 (Odoo) dans un navigateur web EN LIGNE DE COMMANDE choisi
par l'utilisateur.
- _qemu_test_vm : résolution d'IP (_qemu_vm_ip), lancement du navigateur,
message d'aide si la page ne s'affiche pas (Odoo pas démarré / réseau).
- _qemu_choose_cli_browser : liste les navigateurs CLI installés et laisse
choisir ; si aucun, propose d'en installer un (réutilise CLI_BROWSERS /
INSTALLABLE_BROWSERS / browser_install_command de qemu_install_monitor).
- « Lister les images » passe de [10] à [11] ; les entrées de config
suivent à 12+ (branche else inchangée via la liste `real`).
Validé : rendu du menu, choix du navigateur ([2]->elinks, vide->w3m).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Suivi d'installation (qemu_install_monitor.py) :
- Table VM/État LISIBLE : largeurs de colonnes fixes (VM 26, ⚠ 4, État 12,
Durée 7, Disque 8) -> l'État n'est plus tronqué à 3-4 caractères
(« ❌ effacée », « ⏸ en pause » lisibles) ; table défilable (overflow-x,
height 1fr) pour les longs noms et les gros parcs.
- « w » web : offre désormais la LISTE des navigateurs CLI installés
(_choose_browser) pour choisir lequel utiliser, + option [i] installer.
Déploiement (deploy_qemu.py) :
- guest-exec AUTORISÉ : on vide la liste de blocage de qemu-ga
(block-rpcs/blacklist vides — pas allow-rpcs qui est une liste BLANCHE et
casserait les autres RPC), + neutralise /etc/sysconfig/qemu-ga (Fedora).
Installation (todo.py) :
- Profils AVEC Odoo (install_odoo*) UNIQUEMENT : Odoo est enregistré comme
service systemd (erplibre.service, inspiré de script/systemd/
install_daemon.sh) puis enable --now. Pas pour ERPLibre seul / mobile /
Déploiement.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Provisioning (deploy_qemu.py) :
- qemu-guest-agent installé + activé dans le runcmd cloud-init (APRÈS
sshd, « || true » : ne bloque pas le boot si le réseau est lent).
- Canal virtio org.qemu.guest_agent.0 ajouté à virt-install : virsh peut
piloter la VM SANS réseau.
- Extension du FS invité (todo.py) : nouveau tier AGENT INVITÉ
(_qemu_guest_exec via qemu-agent-command guest-exec) entre SSH et la
console série -> étend le FS même sans IP.
Suivi d'installation (qemu_install_monitor.py) :
- Détection d'erreurs dans le log à la complétion (succès OU échec) en
réutilisant la logique de script/test/run_parallel_test.py (sous-chaîne
error/warning + listes d'ignore). Nouvelle colonne « ⚠ » À GAUCHE d'État
(⚠N erreurs / ⚡N avert. / ✓ propre).
- Sommaire de stats EN CHIFFRES (📊 total · ✅ · ❌ · ⏳ · ⏸ · 🗑 · ⚠ · ⚡) ;
CLIC pour déplier le détail (VM en erreur + durées).
- Boutons « p » Pause tout (virsh suspend des VM running) et « o »
Reprendre tout (virsh resume) — les logs continuent (offsets conservés).
Validé headless : succès-avec-erreur -> ⚠1, stats/détail, pause du parc.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Trois améliorations QEMU :
- Extension du FS invité robuste : résolution d'IP avec BATTEMENT
(_qemu_resolve_ips, parallèle, boot émulé lent) au lieu d'un timeout
court ; en cas d'absence d'IP ou d'échec SSH, repli sur la CONSOLE
SÉRIE avec la commande growpart/resize prête à coller (login
erplibre/erplibre). Commande factorisée dans _GROW_FS_REMOTE.
- Temps estimés PAR VERSION : nouveau mon.avg_by_version(distro, version)
et _qemu_stat_avg("version", v, distro) -> suffixe « · ~46s moy (1) »
dans les listes de versions (prompt simple + granulaire).
- i18n : « Versions for » n'avait AUCUNE traduction (restait en anglais).
Ajout FR « Versions des » (+ « Version des ») et capitalisation de la
distro -> « Versions des Ubuntu : ».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deux points :
- Fil d'Ariane depuis la télémétrie : une commande lancée DEPUIS le TUI de
télémétrie ne passait par aucun menu, donc son chemin n'était jamais
affiché. On imprime désormais « 📍 TODO › … › <commande> » (dernier
segment traduit + icône) avant l'exécution, et on enregistre le chemin.
- Arrêt de VM (réduction disque) par SIGNAL : virsh shutdown --mode
acpi,agent (bouton ACPI puis agent invité) au lieu d'un arrêt implicite.
Pendant l'attente, on affiche un compte à rebours du timeout
(« ⏳ arrêt en cours… NNN s restantes ») et le délai max au départ.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Après « virsh list --all », le menu propose désormais :
[1] Infos avancées (vCPU, RAM, disque)
[2] Changer l'état d'une ou plusieurs VM
[Entrée] Rien
Changement d'état (_qemu_change_state) :
- Saisie d'une liste de VM séparée par des virgules (noms ou ID, résolus
via _qemu_domname et validés contre les VM existantes).
- Choix de l'état cible : Ouvrir (start) ou Fermer (shutdown).
- DOUBLE validation (« Appliquer : … ? » puis « Confirmer pour de vrai ? »)
avant d'exécuter virsh start/shutdown sur chaque VM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Si la VM a dû être éteinte pour la réduction, on le NOTE (« La VM a été
éteinte pour le redimensionnement. ») puis on demande (o/N) si on veut la
redémarrer (virsh start sur le nom canonique). Si la VM était déjà éteinte
ou n'a pas eu besoin de l'être (agrandissement à chaud), on ne demande
rien : drapeau was_shut_down positionné uniquement après un arrêt effectif.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Avant, réduire le disque d'une VM allumée affichait seulement « Éteignez
la VM » puis abandonnait. Désormais on DEMANDE (o/N) si on veut l'éteindre
et réessayer :
- _qemu_shutdown_wait : arrêt ACPI gracieux (virsh shutdown), attente
jusqu'à « shut off » (timeout 120s), puis propose un arrêt forcé
(virsh destroy) si l'arrêt traîne.
- _qemu_domname : résout un ID numérique en nom canonique — l'ID
disparaît une fois la VM éteinte, le polling doit utiliser le nom.
Validé : domname(14) -> erplibre-ubuntu-2004.
- Tous les nouveaux prompts affichent la valeur par défaut (o/N).
Une fois éteinte, la réduction (qemu-img resize --shrink) se poursuit
automatiquement après confirmation du risque.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Après « sudo virsh list --all », le menu propose désormais (o/N) un
tableau détaillé par VM : état, vCPU, RAM allouée (Max memory), taille
disque virtuelle et réelle (qemu-img info -U, lit même VM allumée), plus
l'espace total/libre/utilisé du stockage des images (shutil.disk_usage).
- _qemu_list_vms(ask_advanced=False) : seul le menu [4] prompte ;
les appels internes (IP, console, resize, delete) restent inchangés.
- Helpers _qemu_dominfo (vcpu + Max memory) et _qemu_disk_sizes
(virtuel + réel, -U). Validé en réel : 8 vCPU / 8.0G / 30.0G / 2.5G.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
qemu-img info échouait (« Failed to get shared write lock ») sur une VM
running car libvirt tient le lock d'écriture : la taille virtuelle lue
tombait à 0.0 G, et « -10G » donnait alors 0-10 = -10 → « Taille invalide ».
- Ajout de -U (--force-share) aux deux appels qemu-img info (affichage +
_qemu_disk_virtual_bytes) : lecture seule sûre même VM allumée.
- Garde-fou : si la taille reste illisible (0), on abandonne avec un
message clair au lieu de calculer une cible négative.
Le redimensionnement lui-même était déjà correct (virsh blockresize à
chaud si running, qemu-img resize si éteinte) — pas besoin d'éteindre la
VM pour AGRANDIR. Validé : lecture -U renvoie 30.0 G sur une VM running.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
L'historique d'installation est désormais enregistré dans un fichier DÉDIÉ
.venv.erplibre/qemu_install_stats.json (repli ~/.erplibre) : chaque run garde
distro + version + architecture + durée + horodatage (500 derniers).
- record_duration(distro, version, arch, secs) enregistre le run (appelé par
le dashboard à la complétion d'une VM).
- Menus de sélection enrichis : chaque architecture et chaque distribution
affiche la DURÉE MOYENNE d'install historique « · ~5m moy (3) » quand la
donnée existe. La DERNIÈRE install (distro version [arch] — durée) est
rappelée en tête du déploiement.
- eta_reference lit désormais les runs (médiane par arch, repli global).
Nouveaux helpers : avg_by_arch, avg_by_distro, last_run.
Validé : enregistrement + moyennes (amd64 ~5m (2), ubuntu ~13m (3)),
dernière install affichée, distros sans données masquées.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Quand on installe ERPLibre sur une/des VM, on choisit désormais CE qu'on
installe (au lieu de forcer install_odoo_18) :
ERPLibre + Odoo 18/17/16/15/14/13/12 (make install_os && make install_odoo_X)
ERPLibre + toutes les versions Odoo (make install_odoo_all_version)
ERPLibre seulement (sans Odoo) (./script/install/install_erplibre.sh)
ERPLibre mobile (home) (./mobile/install_and_run.sh)
ERPLibre Déploiement (+ QEMU + dev) (install_dev + qemu/libvirt/virtinst)
_qemu_pick_install_profile renvoie la commande finale ; _qemu_erplibre_remote_cmd
l'exécute dans ~/git/erplibre ; le profil est threadé aux installeurs
(monitoré + streamé). Cibles make vérifiées dans les Makefiles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Après exécution d'une commande choisie dans la télémétrie, on propose de
REVENIR (r) ou de quitter. En revenant, la vue ET la position du curseur
sont RESTAURÉES (chemin porté par chaque nœud/carte ; run_tui prend/rend un
`state`).
- Vue Kanban : F4 fait défiler la DISPOSITION —
columns (une rangée de colonnes) / swimlanes (une rangée par menu de
niveau 1) / grid (grille 3 colonnes). F3 bascule Arbre/Kanban.
Validé headless : F3/F4 cyclent les dispositions, sélection -> action+état,
relance avec `state` -> curseur restauré sur le bon nœud.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Le TUI de télémétrie devient aussi un LANCEUR, avec deux vues :
- Sélectionner une COMMANDE (feuille de l'arbre, ou carte du Kanban) +
Entrée -> le TUI se ferme et la commande est EXÉCUTÉE (getattr(self, méthode)
(**kwargs)). Les kwargs littéraux sont extraits du code (ex. Aperçu ->
_qemu_deploy(dry_run=True)), donc la commande est rejouée à l'identique.
- Vue KANBAN : une colonne par menu contenant des commandes (issu du code),
cartes = commandes exécutables + compteur de visites du menu.
- F3 bascule entre vue Arbre et vue Kanban. Résumé mis à jour (Entrée =
exécuter, F3 = vue).
_dispatch capture désormais (méthode, kwargs) ; les feuilles de l'arbre
portent la méthode en data ; run_tui renvoie (méthode, kwargs) à exécuter.
Validé headless : 11 colonnes Kanban, kwargs (dry_run/production_ready)
capturés, bascule F3, capture de l'action à exécuter.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nouvelle entrée « [5] Télémétrie de navigation (TUI) » dans le menu principal.
- Enregistrement : Todo._menu_header (seul constructeur du fil d'Ariane)
appelle todo_telemetry.record(fil) à chaque affichage de menu. Dédup des
ré-affichages consécutifs -> on ne compte que les TRANSITIONS de
navigation. Persistant dans ~/.erplibre/todo_telemetry.json. Best-effort :
ne casse jamais la navigation.
- Visualisation : todo_telemetry.run_tui() ouvre un TUI Textual affichant
l'ARBRE des fonctionnalités visitées (chaque nœud = un menu, avec son
nombre de visites), trié par usage décroissant. Touches : q (quitter),
r (réinitialiser), e (tout déplier). Sommaire : total navigations + menus.
Validé headless : enregistrement + dédup, arbre imbriqué à compteurs,
montage du TUI, peuplement, touches.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nouvelle entrée dans le menu QEMU/KVM (section Gérer). Elle :
- affiche le disque principal (qcow2 via domblklist) + « qemu-img info »
(taille virtuelle + réelle) et l'état de la VM ;
- demande +NG (agrandir), -NG (réduire) ou NG (taille cible) ;
- applique : agrandissement À CHAUD si la VM tourne (virsh blockresize),
sinon qemu-img resize ; réduction via qemu-img resize --shrink (VM éteinte
obligatoire + avertissement fort : le FS invité n'est PAS réduit, risque
de perte de données) ;
- propose ensuite d'étendre le FS invité via SSH (growpart + resize2fs /
xfs_growfs / btrfs, device et type de FS détectés).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>