_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>