erplibre/script/qemu/README.md

412 lines
18 KiB
Markdown
Raw Normal View History

# QEMU/KVM — Linux VM deployment (Ubuntu / Debian / Fedora)
`deploy_qemu.py` deploys a Linux VM (libvirt/KVM) from an official cloud
image, using `qemu-img` + `cloud-init` + `virt-install`. Pick the
distribution with `--distro` (`ubuntu` default, `debian`, `fedora`) and the
release with `--version`; run `--list-images` to see the full catalogue with
minimum specs. It:
1. **Downloads the cloud image by itself** (cached, no double download).
2. Converts it to a dedicated qcow2 working disk and resizes it.
3. Generates `user-data` / `meta-data` and builds the `seed.iso` (cloud-init).
4. Runs `virt-install` importing the disk + the seed as a CD-ROM.
5. Waits for the DHCP lease and prints the SSH command.
## Prerequisites
- A host with KVM available (bare-metal or nested virtualization enabled).
- `sudo` rights (the deployment writes to `/var/lib/libvirt/images` and drives
libvirt).
## Installation
The script **auto-installs the missing pieces it needs**: on first run it
detects your package manager (apt / dnf / pacman / zypper / brew), lists the
missing components (the client tools, **plus the libvirt daemon and the QEMU
system emulator**), asks for confirmation, installs them with `sudo`, then
enables and starts `libvirtd`. Use `-y` to accept automatically or
`--no-install-deps` to disable this behaviour.
To install everything manually on Ubuntu/Debian (recommended full KVM stack):
```bash
sudo apt install qemu-utils virtinst libvirt-clients cloud-image-utils \
libvirt-daemon-system qemu-system-x86
sudo systemctl enable --now libvirtd
sudo usermod -aG libvirt,kvm "$USER" # re-login / reconnectez-vous
```
`libvirt-daemon-system` provides the `libvirtd` daemon (and the
`/var/run/libvirt/libvirt-sock` socket) and `qemu-system-x86` the emulator —
without them `virt-install` fails with *"Failed to connect socket to
'/var/run/libvirt/libvirt-sock'"*. The script installs and starts them for
you; this manual command is only needed if you prefer to prepare the host
yourself or run with `--no-install-deps`.
## Usage
Simplest form — the image is downloaded automatically (path derived from
`--version`, cached in `/var/lib/libvirt/images/iso`):
```bash
sudo ./script/qemu/deploy_qemu.py --name test-vm --version 24.04 \
--ssh-key ~/.ssh/id_ed25519.pub
```
Download (and verify) an image without creating a VM:
```bash
sudo ./script/qemu/deploy_qemu.py --download-only --version 24.04 --verify
```
Deploy with an interactive password instead of an SSH key:
```bash
sudo ./script/qemu/deploy_qemu.py --name test-vm --version 24.04 --ask-password
```
Larger VM (8 GB RAM, 8 vCPU, 120 GB disk), overwriting an existing disk:
```bash
sudo ./script/qemu/deploy_qemu.py --name test-vm --version 24.04 \
--memory 8192 --vcpus 8 --disk-size 120G --ask-password --force
```
Preview what would happen, without doing anything (no sudo, no download):
```bash
./script/qemu/deploy_qemu.py --name test-vm --version 24.04 --dry-run
```
Non-interactive deployment (accept dependency install automatically):
```bash
sudo ./script/qemu/deploy_qemu.py --name test-vm --version 24.04 \
--ssh-key ~/.ssh/id_ed25519.pub -y
```
[UPD] support: drop Ubuntu 20.04/22.04, add AlmaLinux and Rocky Ubuntu 20.04 and 22.04 leave EVERY architecture, not just s390x. pikepdf needs qpdf 12.2, whose build requires C++20, while focal ships GCC 9 and publishes no g++-10 for s390x at all. Python 3.8, node 10, cargo 0.67 and OpenSSL 1.1.1 each had a workaround; the pile of them did not. 18.04 follows, already off the lists. The refusal lands before any apt, this script also serving existing machines. AlmaLinux 9 and 10, Rocky 9 and 10 join the catalog on all four architectures: the twelve "latest" URLs were opened, with no index to parse unlike Fedora. They would have booted unreachable though -- the cloud-config forced "groups: users, sudo", but the RHEL family has no sudo group, only wheel, and an unknown group makes useradd fail, hence no password and no key. The very trap already known for Debian, repeated elsewhere. Host side, EPEL and CRB are enabled: without them most -devel packages are missing, silently. The server / graphical choice gains Cinnamon, the Linux Mint desktop, from the distribution's own repositories. Mint's repository is set aside: plain HTTP, and i386/amd64 only, which would rule out arm64 and s390x. Along the way, dnf now installs an ENVIRONMENT rather than a group -- "gnome-desktop" brings gdm and gnome-shell but not base-x, hence no X server. --- FR --- Ubuntu 20.04 et 22.04 partent de TOUTES les architectures, pas seulement de s390x. pikepdf réclame qpdf 12.2, dont la compilation exige C++20, quand focal livre GCC 9 et ne publie même pas de g++-10 pour s390x. Python 3.8, node 10, cargo 0.67 et OpenSSL 1.1.1 avaient chacun leur contournement ; leur accumulation, non. 18.04 suit, déjà hors des listes. Le refus tombe avant tout apt, ce script servant aussi les machines existantes. AlmaLinux 9 et 10, Rocky 9 et 10 entrent au catalogue, sur les quatre architectures : les douze URL « latest » ont été ouvertes, aucun index à analyser contrairement à Fedora. Elles auraient pourtant démarré inaccessibles — le cloud-config imposait « groups: users, sudo », or la famille RHEL n'a pas de groupe sudo mais wheel, et un groupe inconnu fait échouer useradd, donc ni mot de passe ni clé. C'est le piège déjà connu pour Debian, reproduit ailleurs. Côté hôte, EPEL et CRB sont activés : sans eux la plupart des -devel manquent, en silence. Le choix serveur / graphique gagne Cinnamon, le bureau de Linux Mint, depuis les dépôts de la distribution. Le dépôt de Mint lui-même est écarté : il est en HTTP nu et ne publie que i386 et amd64, ce qui exclurait arm64 et s390x. Au passage, dnf installe désormais un ENVIRONNEMENT et non un groupe — « gnome-desktop » apporte gdm et gnome-shell mais pas base-x, donc pas de serveur X. Assisted-by: Claude Opus 5
2026-08-11 18:46:11 -04:00
Catalog, per architecture (`deploy_qemu.py` is the source of truth):
| Distro | Versions | amd64 | arm64 | s390x |
|---|---|:-:|:-:|:-:|
| ubuntu | `24.04` (default), `25.10`, `26.04` | ✔ | ✔ | ✔ |
| debian | `11`, `12` (default), `13` | ✔ | ✔ | — |
[ADD] catalogue: Fedora 43 for s390x Fedora does publish an s390x cloud image, just elsewhere: the arch is SECONDARY there, its images live under "fedora-secondary" rather than /pub/fedora/linux/. The catalog comment read "404" -- it was the wrong tree. It brings what nothing else has on this architecture: qpdf 12.2.0, exactly the pikepdf 10 threshold. The qpdf build, half an hour under emulation, disappears. With GCC 15, Python 3.14 and cargo 1.90, none of the s390x workarounds fire there. Only 43 is kept: Fedora builds s390x for the current release only. 41 and 42 return 404 on the master mirror, 44 sits on third-party mirrors only. Hence a positive per-architecture declaration, read by both interfaces -- the catalog never offers what deployment would refuse. --- FR --- Fedora publie bien une image cloud s390x, mais ailleurs : l'architecture est SECONDAIRE chez elle, ses images vivent sous « fedora-secondary » et non sous /pub/fedora/linux/. Le commentaire du catalogue disait « 404 » — c'était la mauvaise arborescence. Elle apporte ce qu'aucune autre n'a sur cette architecture : qpdf 12.2.0, soit exactement le seuil de pikepdf 10. La compilation de qpdf, une demi- heure sous émulation, disparaît. Avec GCC 15, Python 3.14 et cargo 1.90, aucun des contournements s390x ne s'y déclenche. Seule la 43 est retenue : Fedora ne construit s390x que pour la version courante. Les 41 et 42 rendent 404 sur le miroir maître, la 44 n'est que sur certains miroirs tiers. D'où une déclaration positive par architecture, que les deux interfaces relisent — le catalogue ne propose pas ce que le déploiement refuserait. Assisted-by: Claude Opus 5
2026-08-11 19:55:09 -04:00
| fedora | `41`, `42` (default), `43`, `44` | ✔ | ✔ | `43` only |
[UPD] support: drop Ubuntu 20.04/22.04, add AlmaLinux and Rocky Ubuntu 20.04 and 22.04 leave EVERY architecture, not just s390x. pikepdf needs qpdf 12.2, whose build requires C++20, while focal ships GCC 9 and publishes no g++-10 for s390x at all. Python 3.8, node 10, cargo 0.67 and OpenSSL 1.1.1 each had a workaround; the pile of them did not. 18.04 follows, already off the lists. The refusal lands before any apt, this script also serving existing machines. AlmaLinux 9 and 10, Rocky 9 and 10 join the catalog on all four architectures: the twelve "latest" URLs were opened, with no index to parse unlike Fedora. They would have booted unreachable though -- the cloud-config forced "groups: users, sudo", but the RHEL family has no sudo group, only wheel, and an unknown group makes useradd fail, hence no password and no key. The very trap already known for Debian, repeated elsewhere. Host side, EPEL and CRB are enabled: without them most -devel packages are missing, silently. The server / graphical choice gains Cinnamon, the Linux Mint desktop, from the distribution's own repositories. Mint's repository is set aside: plain HTTP, and i386/amd64 only, which would rule out arm64 and s390x. Along the way, dnf now installs an ENVIRONMENT rather than a group -- "gnome-desktop" brings gdm and gnome-shell but not base-x, hence no X server. --- FR --- Ubuntu 20.04 et 22.04 partent de TOUTES les architectures, pas seulement de s390x. pikepdf réclame qpdf 12.2, dont la compilation exige C++20, quand focal livre GCC 9 et ne publie même pas de g++-10 pour s390x. Python 3.8, node 10, cargo 0.67 et OpenSSL 1.1.1 avaient chacun leur contournement ; leur accumulation, non. 18.04 suit, déjà hors des listes. Le refus tombe avant tout apt, ce script servant aussi les machines existantes. AlmaLinux 9 et 10, Rocky 9 et 10 entrent au catalogue, sur les quatre architectures : les douze URL « latest » ont été ouvertes, aucun index à analyser contrairement à Fedora. Elles auraient pourtant démarré inaccessibles — le cloud-config imposait « groups: users, sudo », or la famille RHEL n'a pas de groupe sudo mais wheel, et un groupe inconnu fait échouer useradd, donc ni mot de passe ni clé. C'est le piège déjà connu pour Debian, reproduit ailleurs. Côté hôte, EPEL et CRB sont activés : sans eux la plupart des -devel manquent, en silence. Le choix serveur / graphique gagne Cinnamon, le bureau de Linux Mint, depuis les dépôts de la distribution. Le dépôt de Mint lui-même est écarté : il est en HTTP nu et ne publie que i386 et amd64, ce qui exclurait arm64 et s390x. Au passage, dnf installe désormais un ENVIRONNEMENT et non un groupe — « gnome-desktop » apporte gdm et gnome-shell mais pas base-x, donc pas de serveur X. Assisted-by: Claude Opus 5
2026-08-11 18:46:11 -04:00
| almalinux | `9` (default), `10` | ✔ | ✔ | ✔ |
| rocky | `9`, `10` (default) | ✔ | ✔ | ✔ |
[ADD] qemu: openSUSE Leap 16.0, numbered, as the default The catalogue only offered openSUSE Tumbleweed, a rolling release with no version number. Nearly every openSUSE incident in this series came from that: the cloud image is a snapshot lagging behind its own repos, hence the mandatory "zypper dup", and two VMs deployed the same day saw git-daemon 2.54 on s390x against 2.55 on amd64. For an ERP platform, that is not what should be offered by default. Leap 16.0 is numbered, stable, and publishes the three architectures that matter -- verified, x86_64, aarch64 and s390x all 200. It also unifies its tree: no separate /ports/, unlike Tumbleweed, whose equivalent paths 404 for Leap. Tumbleweed stays on offer, as a bellwether for breakage to come. Two things separate them in the scripts: the repository path, and "dup" -- which on Leap means CHANGING version, not updating. --- FR --- Le catalogue n'offrait qu'openSUSE Tumbleweed, une rolling sans numéro de version. Presque tous les incidents openSUSE de la série venaient de là : l'image cloud est un instantané en retard sur ses dépôts, d'où le « zypper dup » obligatoire, et deux VM déployées le même jour ont vu git-daemon 2.54 sur s390x contre 2.55 sur amd64. Pour une plateforme ERP, ce n'est pas ce qu'on veut proposer par défaut. Leap 16.0 est numérotée, stable, et publie les trois architectures qui comptent — vérifié, x86_64, aarch64 et s390x en 200. Elle unifie de plus son arbre : pas de /ports/ séparé, contrairement à Tumbleweed, dont les chemins équivalents rendent 404 pour Leap. Tumbleweed reste offerte, comme banc d'essai des ruptures à venir. Deux points les séparent dans les scripts : le chemin des dépôts, et « dup » — qui sur Leap sert à CHANGER de version, pas à mettre à jour. Assisted-by: Claude Opus 5
2026-08-12 01:16:17 -04:00
| opensuse | `16.0` (default), `tumbleweed` | ✔ | ✔ | ✔ |
[UPD] support: drop Ubuntu 20.04/22.04, add AlmaLinux and Rocky Ubuntu 20.04 and 22.04 leave EVERY architecture, not just s390x. pikepdf needs qpdf 12.2, whose build requires C++20, while focal ships GCC 9 and publishes no g++-10 for s390x at all. Python 3.8, node 10, cargo 0.67 and OpenSSL 1.1.1 each had a workaround; the pile of them did not. 18.04 follows, already off the lists. The refusal lands before any apt, this script also serving existing machines. AlmaLinux 9 and 10, Rocky 9 and 10 join the catalog on all four architectures: the twelve "latest" URLs were opened, with no index to parse unlike Fedora. They would have booted unreachable though -- the cloud-config forced "groups: users, sudo", but the RHEL family has no sudo group, only wheel, and an unknown group makes useradd fail, hence no password and no key. The very trap already known for Debian, repeated elsewhere. Host side, EPEL and CRB are enabled: without them most -devel packages are missing, silently. The server / graphical choice gains Cinnamon, the Linux Mint desktop, from the distribution's own repositories. Mint's repository is set aside: plain HTTP, and i386/amd64 only, which would rule out arm64 and s390x. Along the way, dnf now installs an ENVIRONMENT rather than a group -- "gnome-desktop" brings gdm and gnome-shell but not base-x, hence no X server. --- FR --- Ubuntu 20.04 et 22.04 partent de TOUTES les architectures, pas seulement de s390x. pikepdf réclame qpdf 12.2, dont la compilation exige C++20, quand focal livre GCC 9 et ne publie même pas de g++-10 pour s390x. Python 3.8, node 10, cargo 0.67 et OpenSSL 1.1.1 avaient chacun leur contournement ; leur accumulation, non. 18.04 suit, déjà hors des listes. Le refus tombe avant tout apt, ce script servant aussi les machines existantes. AlmaLinux 9 et 10, Rocky 9 et 10 entrent au catalogue, sur les quatre architectures : les douze URL « latest » ont été ouvertes, aucun index à analyser contrairement à Fedora. Elles auraient pourtant démarré inaccessibles — le cloud-config imposait « groups: users, sudo », or la famille RHEL n'a pas de groupe sudo mais wheel, et un groupe inconnu fait échouer useradd, donc ni mot de passe ni clé. C'est le piège déjà connu pour Debian, reproduit ailleurs. Côté hôte, EPEL et CRB sont activés : sans eux la plupart des -devel manquent, en silence. Le choix serveur / graphique gagne Cinnamon, le bureau de Linux Mint, depuis les dépôts de la distribution. Le dépôt de Mint lui-même est écarté : il est en HTTP nu et ne publie que i386 et amd64, ce qui exclurait arm64 et s390x. Au passage, dnf installe désormais un ENVIRONNEMENT et non un groupe — « gnome-desktop » apporte gdm et gnome-shell mais pas base-x, donc pas de serveur X. Assisted-by: Claude Opus 5
2026-08-11 18:46:11 -04:00
| arch | `latest` | ✔ | — | — |
[ADD] catalogue: Fedora 43 for s390x Fedora does publish an s390x cloud image, just elsewhere: the arch is SECONDARY there, its images live under "fedora-secondary" rather than /pub/fedora/linux/. The catalog comment read "404" -- it was the wrong tree. It brings what nothing else has on this architecture: qpdf 12.2.0, exactly the pikepdf 10 threshold. The qpdf build, half an hour under emulation, disappears. With GCC 15, Python 3.14 and cargo 1.90, none of the s390x workarounds fire there. Only 43 is kept: Fedora builds s390x for the current release only. 41 and 42 return 404 on the master mirror, 44 sits on third-party mirrors only. Hence a positive per-architecture declaration, read by both interfaces -- the catalog never offers what deployment would refuse. --- FR --- Fedora publie bien une image cloud s390x, mais ailleurs : l'architecture est SECONDAIRE chez elle, ses images vivent sous « fedora-secondary » et non sous /pub/fedora/linux/. Le commentaire du catalogue disait « 404 » — c'était la mauvaise arborescence. Elle apporte ce qu'aucune autre n'a sur cette architecture : qpdf 12.2.0, soit exactement le seuil de pikepdf 10. La compilation de qpdf, une demi- heure sous émulation, disparaît. Avec GCC 15, Python 3.14 et cargo 1.90, aucun des contournements s390x ne s'y déclenche. Seule la 43 est retenue : Fedora ne construit s390x que pour la version courante. Les 41 et 42 rendent 404 sur le miroir maître, la 44 n'est que sur certains miroirs tiers. D'où une déclaration positive par architecture, que les deux interfaces relisent — le catalogue ne propose pas ce que le déploiement refuserait. Assisted-by: Claude Opus 5
2026-08-11 19:55:09 -04:00
Fedora builds s390x only for the current release, and on a separate tree
(`fedora-secondary`) — hence the single version there.
[ADD] qemu: openSUSE Leap 16.0, numbered, as the default The catalogue only offered openSUSE Tumbleweed, a rolling release with no version number. Nearly every openSUSE incident in this series came from that: the cloud image is a snapshot lagging behind its own repos, hence the mandatory "zypper dup", and two VMs deployed the same day saw git-daemon 2.54 on s390x against 2.55 on amd64. For an ERP platform, that is not what should be offered by default. Leap 16.0 is numbered, stable, and publishes the three architectures that matter -- verified, x86_64, aarch64 and s390x all 200. It also unifies its tree: no separate /ports/, unlike Tumbleweed, whose equivalent paths 404 for Leap. Tumbleweed stays on offer, as a bellwether for breakage to come. Two things separate them in the scripts: the repository path, and "dup" -- which on Leap means CHANGING version, not updating. --- FR --- Le catalogue n'offrait qu'openSUSE Tumbleweed, une rolling sans numéro de version. Presque tous les incidents openSUSE de la série venaient de là : l'image cloud est un instantané en retard sur ses dépôts, d'où le « zypper dup » obligatoire, et deux VM déployées le même jour ont vu git-daemon 2.54 sur s390x contre 2.55 sur amd64. Pour une plateforme ERP, ce n'est pas ce qu'on veut proposer par défaut. Leap 16.0 est numérotée, stable, et publie les trois architectures qui comptent — vérifié, x86_64, aarch64 et s390x en 200. Elle unifie de plus son arbre : pas de /ports/ séparé, contrairement à Tumbleweed, dont les chemins équivalents rendent 404 pour Leap. Tumbleweed reste offerte, comme banc d'essai des ruptures à venir. Deux points les séparent dans les scripts : le chemin des dépôts, et « dup » — qui sur Leap sert à CHANGER de version, pas à mettre à jour. Assisted-by: Claude Opus 5
2026-08-12 01:16:17 -04:00
`opensuse` covers two distinct products, not two versions of one. Leap `16.0`
is numbered and stable (SLE base) and is the default. `tumbleweed` is the
rolling one, kept as a bellwether for breakage to come: its snapshot drift is
real, and it demands a full `zypper dup` before anything can be installed.
Both ship a qpdf above the pikepdf threshold, so the half-hour qpdf build
never runs there — which matters under s390x emulation.
[ADD] install: openSUSE support, mirrors and packages Tumbleweed is the only catalog entry whose qpdf already clears the pikepdf threshold: 12.3.2 against 12.2 required, so the half-hour qpdf build under s390x emulation never runs there. It is also the only family foreign to both RHEL and Debian, and it brings zypper, absent from the repository: wired into the install_dev.sh dispatch, a dependency script, the remote bootstrap and the desktop block. Four SUSE traps, all met on real machines. Global options precede the subcommand, and misplaced ones abort the call; without "--auto-agree-with-licenses" zypper waits for an answer nobody gives in a detached install. A compat package that PROVIDES another under a different name makes zypper raise a conflict and drop the whole batch, so only what nothing already provides is requested. pkg-config no longer exists as an RPM, only as a capability. And a single mirror, unreachable one day, sent everyone back to Europe -- three are probed in order now. CentOS Stream is dropped again: it served as a canary but 10 duplicates what Alma and Rocky already cover. --- FR --- Tumbleweed est la seule entrée du catalogue dont qpdf franchit déjà le seuil de pikepdf : 12.3.2 contre 12.2 exigé, si bien que la demi-heure de compilation de qpdf sous émulation s390x ne s'y déclenche jamais. C'est aussi la seule famille étrangère à RHEL comme à Debian, et elle apporte zypper, absent du dépôt : câblé dans l'aiguillage d'install_dev.sh, un script de dépendances, l'amorçage distant et le bloc bureau. Quatre pièges SUSE, tous rencontrés sur machine réelle. Les options globales précèdent la sous-commande, mal placées elles arrêtent l'appel ; sans « --auto-agree-with-licenses » zypper attend une réponse que personne ne donne dans une installation détachée. Un paquet compat qui FOURNIT un autre sous un nom différent fait lever un conflit à zypper, qui abandonne le lot entier : on ne demande donc que ce que rien ne fournit déjà. pkg-config n'existe plus comme RPM, seulement comme capacité. Et un miroir unique, injoignable un jour, renvoyait tout le monde en Europe — trois sont désormais sondés dans l'ordre. CentOS Stream repart : il servait de canari, mais 10 double ce qu'Alma et Rocky couvrent déjà. Assisted-by: Claude Opus 5
2026-08-11 20:38:12 -04:00
[UPD] support: drop Ubuntu 20.04/22.04, add AlmaLinux and Rocky Ubuntu 20.04 and 22.04 leave EVERY architecture, not just s390x. pikepdf needs qpdf 12.2, whose build requires C++20, while focal ships GCC 9 and publishes no g++-10 for s390x at all. Python 3.8, node 10, cargo 0.67 and OpenSSL 1.1.1 each had a workaround; the pile of them did not. 18.04 follows, already off the lists. The refusal lands before any apt, this script also serving existing machines. AlmaLinux 9 and 10, Rocky 9 and 10 join the catalog on all four architectures: the twelve "latest" URLs were opened, with no index to parse unlike Fedora. They would have booted unreachable though -- the cloud-config forced "groups: users, sudo", but the RHEL family has no sudo group, only wheel, and an unknown group makes useradd fail, hence no password and no key. The very trap already known for Debian, repeated elsewhere. Host side, EPEL and CRB are enabled: without them most -devel packages are missing, silently. The server / graphical choice gains Cinnamon, the Linux Mint desktop, from the distribution's own repositories. Mint's repository is set aside: plain HTTP, and i386/amd64 only, which would rule out arm64 and s390x. Along the way, dnf now installs an ENVIRONMENT rather than a group -- "gnome-desktop" brings gdm and gnome-shell but not base-x, hence no X server. --- FR --- Ubuntu 20.04 et 22.04 partent de TOUTES les architectures, pas seulement de s390x. pikepdf réclame qpdf 12.2, dont la compilation exige C++20, quand focal livre GCC 9 et ne publie même pas de g++-10 pour s390x. Python 3.8, node 10, cargo 0.67 et OpenSSL 1.1.1 avaient chacun leur contournement ; leur accumulation, non. 18.04 suit, déjà hors des listes. Le refus tombe avant tout apt, ce script servant aussi les machines existantes. AlmaLinux 9 et 10, Rocky 9 et 10 entrent au catalogue, sur les quatre architectures : les douze URL « latest » ont été ouvertes, aucun index à analyser contrairement à Fedora. Elles auraient pourtant démarré inaccessibles — le cloud-config imposait « groups: users, sudo », or la famille RHEL n'a pas de groupe sudo mais wheel, et un groupe inconnu fait échouer useradd, donc ni mot de passe ni clé. C'est le piège déjà connu pour Debian, reproduit ailleurs. Côté hôte, EPEL et CRB sont activés : sans eux la plupart des -devel manquent, en silence. Le choix serveur / graphique gagne Cinnamon, le bureau de Linux Mint, depuis les dépôts de la distribution. Le dépôt de Mint lui-même est écarté : il est en HTTP nu et ne publie que i386 et amd64, ce qui exclurait arm64 et s390x. Au passage, dnf installe désormais un ENVIRONNEMENT et non un groupe — « gnome-desktop » apporte gdm et gnome-shell mais pas base-x, donc pas de serveur X. Assisted-by: Claude Opus 5
2026-08-11 18:46:11 -04:00
Provide an explicit image path as a positional argument to override the
automatic download location.
Ubuntu `20.04` and `22.04` were **dropped on every architecture**: pikepdf
needs qpdf 12.2, whose build requires C++20, and focal ships GCC 9 — it does
not even publish `g++-10` for s390x. Python 3.8, node 10, cargo 0.67 and
OpenSSL 1.1.1 each had a workaround; the pile of them did not.
## After deployment
```bash
virsh list --all
virsh console test-vm # Ctrl+] to quit / pour quitter
virsh domifaddr test-vm --source lease # find the IP / trouver l'IP
ssh erplibre@<IP>
```
The default user is `erplibre` (change it with `--user`).
## Via the TODO menu
The script is integrated into the interactive assistant. Run `make todo` (or
`./script/todo/todo.py`), then go to **Execute → Deploy → QEMU/KVM - Deploy an
Ubuntu VM (libvirt)**. From there you can deploy a VM, preview a dry-run,
download an image, list VMs and show a VM IP address — the menu asks for the
parameters and builds the command for you.
[ADD] todo qemu: offer PyCharm, Android Studio and GNOME extensions A graphical VM was a desktop and nothing else: every developer tool had to be installed by hand afterwards. A check list now carries them, filtered per machine — Android Studio is x86_64 only, Google publishes no Linux aarch64 build — and their disk cost reaches the plan before any qcow2 is created. They are installed BEFORE the clone: PyCharm writes the .idea/ of the repository, and the install that follows is what runs pycharm_configuration.py, through update_env_version.pycharm_update(). Its launcher is named studio, which is enough to conclude the install failed; it now answers to android-studio too. GNOME extensions come from the site by UUID, per running Shell: the same endpoint serves gTile v59 for GNOME 46 and v62 for 48. Checked with stubs: every tool can fail and the install's exit code still wins, 45 tests. --- FR --- Une VM graphique n'était qu'un bureau : chaque outil de développement restait à poser à la main. Une liste à cocher les porte, filtrés machine par machine — Android Studio n'existe qu'en x86_64, Google ne publiant aucune archive Linux aarch64 — et leur place disque atteint le plan avant qu'un seul qcow2 ne soit créé. Ils sont posés AVANT le clone : PyCharm écrit le .idea/ du dépôt, et c'est l'installation qui suit qui lance pycharm_configuration.py, via update_env_version.pycharm_update(). Son lanceur s'appelle studio, ce qui suffit à conclure à un échec ; il répond désormais aussi à android-studio. Les extensions GNOME viennent du site par UUID, selon le Shell qui tourne : le même point d'entrée sert gTile v59 pour GNOME 46 et v62 pour 48. Vérifié avec des leurres : chaque outil peut échouer sans que le code de sortie de l'installation ne change, 45 tests. Assisted-by: Claude Opus 5
2026-08-17 22:57:56 -04:00
When a VM is graphical, the menu also offers a **check list of development
[FIX] todo qemu: open the PyCharm project without a screen, and unlicensed Two measurements, one after the other. The unified PyCharm that code=PCC&latest now serves stops on its licence: the log says NoValidIdeLicense then "Get licenses: request requires authentication", and no project is ever opened — so no .idea, so nothing for the install to configure. The Community line asks for no account and is still patched (2025.2.6.2 on 2026-07-29); it is resolved from the release feed, so no version is frozen here. That build then froze 1.3 s after startup: the trust dialog, invisible under Xvfb and waiting for a click. idea.trust.all.projects unblocks it. Checked on an Ubuntu 26.04 VM: .idea complete in 195 s, and pycharm_configuration.py writes its exclusions into erplibre.iml. --- FR --- Deux mesures, l'une après l'autre. Le PyCharm unifié que sert désormais code=PCC&latest s'arrête sur sa licence : le journal dit NoValidIdeLicense puis « Get licenses: request requires authentication », et aucun projet ne s'ouvre — donc pas de .idea, donc rien à configurer pour l'installation. La ligne Community ne demande aucun compte et reste corrigée (2025.2.6.2 le 2026-07-29) ; elle est résolue depuis le flux des versions, sans qu'aucun numéro ne soit figé ici. Ce build se figeait ensuite 1,3 s après le démarrage : la fenêtre de confiance, invisible sous Xvfb et attendant un clic. idea.trust.all.projects la lève. Vérifié sur une VM Ubuntu 26.04 : .idea complet en 195 s, et pycharm_configuration.py écrit ses exclusions dans erplibre.iml. Assisted-by: Claude Opus 5
2026-08-17 23:22:38 -04:00
tools**: PyCharm Community (installed from the official
JetBrains archive into `/opt/pycharm`, its launcher opening the ERPLibre
checkout — the Community line, because the unified 2025.3 build stops on a
licence screen and never opens a project), Android
[ADD] todo qemu: offer PyCharm, Android Studio and GNOME extensions A graphical VM was a desktop and nothing else: every developer tool had to be installed by hand afterwards. A check list now carries them, filtered per machine — Android Studio is x86_64 only, Google publishes no Linux aarch64 build — and their disk cost reaches the plan before any qcow2 is created. They are installed BEFORE the clone: PyCharm writes the .idea/ of the repository, and the install that follows is what runs pycharm_configuration.py, through update_env_version.pycharm_update(). Its launcher is named studio, which is enough to conclude the install failed; it now answers to android-studio too. GNOME extensions come from the site by UUID, per running Shell: the same endpoint serves gTile v59 for GNOME 46 and v62 for 48. Checked with stubs: every tool can fail and the install's exit code still wins, 45 tests. --- FR --- Une VM graphique n'était qu'un bureau : chaque outil de développement restait à poser à la main. Une liste à cocher les porte, filtrés machine par machine — Android Studio n'existe qu'en x86_64, Google ne publiant aucune archive Linux aarch64 — et leur place disque atteint le plan avant qu'un seul qcow2 ne soit créé. Ils sont posés AVANT le clone : PyCharm écrit le .idea/ du dépôt, et c'est l'installation qui suit qui lance pycharm_configuration.py, via update_env_version.pycharm_update(). Son lanceur s'appelle studio, ce qui suffit à conclure à un échec ; il répond désormais aussi à android-studio. Les extensions GNOME viennent du site par UUID, selon le Shell qui tourne : le même point d'entrée sert gTile v59 pour GNOME 46 et v62 pour 48. Vérifié avec des leurres : chaque outil peut échouer sans que le code de sortie de l'installation ne change, 45 tests. Assisted-by: Claude Opus 5
2026-08-17 22:57:56 -04:00
Studio (`/opt/android-studio`, command `studio` or `android-studio`; x86_64
only — Google publishes no Linux aarch64 build) and a set of suggested GNOME extensions.
The extension packages of the distribution are installed but left disabled —
their UUID is not reliably known, and the Extension Manager is there to pick
from. Three extensions named by UUID are installed **and enabled**, straight
from extensions.gnome.org: **gTile**, **Freon** and **Tracker**. The archive
is fetched for the GNOME Shell version actually running in the VM — the same
endpoint serves gTile v59 for GNOME 46 and v62 for GNOME 48, so a frozen URL
would install a build made for another release. A mismatched build is never
loaded by GNOME anyway: it compares `metadata.json` with its own version and
shows the extension as outdated rather than breaking the session.
The tools are installed **before** the clone and the ERPLibre install, and
the order matters: PyCharm writes the repository's `.idea/` the first time it
opens the project, and the install that follows runs
`pycharm_configuration.py` on it (`update_env_version.pycharm_update()`,
[FIX] todo qemu: open the PyCharm project without a screen, and unlicensed Two measurements, one after the other. The unified PyCharm that code=PCC&latest now serves stops on its licence: the log says NoValidIdeLicense then "Get licenses: request requires authentication", and no project is ever opened — so no .idea, so nothing for the install to configure. The Community line asks for no account and is still patched (2025.2.6.2 on 2026-07-29); it is resolved from the release feed, so no version is frozen here. That build then froze 1.3 s after startup: the trust dialog, invisible under Xvfb and waiting for a click. idea.trust.all.projects unblocks it. Checked on an Ubuntu 26.04 VM: .idea complete in 195 s, and pycharm_configuration.py writes its exclusions into erplibre.iml. --- FR --- Deux mesures, l'une après l'autre. Le PyCharm unifié que sert désormais code=PCC&latest s'arrête sur sa licence : le journal dit NoValidIdeLicense puis « Get licenses: request requires authentication », et aucun projet ne s'ouvre — donc pas de .idea, donc rien à configurer pour l'installation. La ligne Community ne demande aucun compte et reste corrigée (2025.2.6.2 le 2026-07-29) ; elle est résolue depuis le flux des versions, sans qu'aucun numéro ne soit figé ici. Ce build se figeait ensuite 1,3 s après le démarrage : la fenêtre de confiance, invisible sous Xvfb et attendant un clic. idea.trust.all.projects la lève. Vérifié sur une VM Ubuntu 26.04 : .idea complet en 195 s, et pycharm_configuration.py écrit ses exclusions dans erplibre.iml. Assisted-by: Claude Opus 5
2026-08-17 23:22:38 -04:00
which skips silently when there is no `.idea` yet). That first open is automated: PyCharm runs once under
Xvfb — a virtual framebuffer inside the guest, so the orchestrating host
needs no graphics at all — with the trust, privacy and data-sharing dialogs
answered in advance. Measured on an Ubuntu 26.04 VM with 16 GB: `.idea/` is
written in 195 s, and the install then adds its exclusions to the `.iml`.
When Xvfb is unavailable or the IDE does not get there in five minutes, the
log says so and the install carries on.
[ADD] todo qemu: offer PyCharm, Android Studio and GNOME extensions A graphical VM was a desktop and nothing else: every developer tool had to be installed by hand afterwards. A check list now carries them, filtered per machine — Android Studio is x86_64 only, Google publishes no Linux aarch64 build — and their disk cost reaches the plan before any qcow2 is created. They are installed BEFORE the clone: PyCharm writes the .idea/ of the repository, and the install that follows is what runs pycharm_configuration.py, through update_env_version.pycharm_update(). Its launcher is named studio, which is enough to conclude the install failed; it now answers to android-studio too. GNOME extensions come from the site by UUID, per running Shell: the same endpoint serves gTile v59 for GNOME 46 and v62 for 48. Checked with stubs: every tool can fail and the install's exit code still wins, 45 tests. --- FR --- Une VM graphique n'était qu'un bureau : chaque outil de développement restait à poser à la main. Une liste à cocher les porte, filtrés machine par machine — Android Studio n'existe qu'en x86_64, Google ne publiant aucune archive Linux aarch64 — et leur place disque atteint le plan avant qu'un seul qcow2 ne soit créé. Ils sont posés AVANT le clone : PyCharm écrit le .idea/ du dépôt, et c'est l'installation qui suit qui lance pycharm_configuration.py, via update_env_version.pycharm_update(). Son lanceur s'appelle studio, ce qui suffit à conclure à un échec ; il répond désormais aussi à android-studio. Les extensions GNOME viennent du site par UUID, selon le Shell qui tourne : le même point d'entrée sert gTile v59 pour GNOME 46 et v62 pour 48. Vérifié avec des leurres : chaque outil peut échouer sans que le code de sortie de l'installation ne change, 45 tests. Assisted-by: Claude Opus 5
2026-08-17 22:57:56 -04:00
[ADD] todo qemu: build and test the mobile app, and fail the VM if it breaks A VM was declared ready without anything proving it. The mobile option now adds the repository to the manifest (additive, so it rides along an Odoo 18 install), runs the mobile repository's own install-android.sh — licences accepted included — then npm ci, vite build, cap sync, gradlew assembleDebug and npm test. A failure fails the VM: the exit code reaches the dashboard. Two gaps in that upstream installer had to be filled: unzip and wget, which no cloud image ships, and the SDK platform — it installs android-34 while variables.gradle asks for compileSdk 36, so the number is read from the file rather than frozen. Gradle writes tens of megabytes and hundreds of harmless lines carrying the word error: that output goes to a file of its own, and the log gets the named cause instead. --- FR --- Une VM était déclarée prête sans que rien ne le prouve. L'option mobile ajoute maintenant le dépôt au manifeste (additif, donc il accompagne une installation Odoo 18), lance l'install-android.sh du dépôt mobile lui-même — licences acceptées comprises —, puis npm ci, vite build, cap sync, gradlew assembleDebug et npm test. Un échec fait échouer la VM : le code de sortie remonte au tableau de bord. Deux manques de cet installateur amont ont dû être comblés : unzip et wget, qu'aucune image cloud ne livre, et la plateforme SDK — il pose android-34 quand variables.gradle réclame compileSdk 36, d'où le chiffre lu dans le fichier plutôt que figé. Gradle écrit des dizaines de mégaoctets et des centaines de lignes anodines portant le mot error : cette sortie part dans un fichier à part, et le journal reçoit la cause nommée. Assisted-by: Claude Opus 5
2026-08-18 00:50:08 -04:00
A fourth one needs no desktop at all: **ERPLibre mobile (build)**. It adds
the mobile repository to the manifest (which is additive, so it coexists with
an Odoo 18 install), runs the repository's own `install-android.sh` — JDK 17,
command-line tools, SDK licences accepted, NDK, whisper.cpp and sentencepiece
— then builds: `npm ci`, `vite build`, `cap sync`, `gradlew assembleDebug`,
and finally `npm test`. **A failed build fails the VM**: the exit code reaches
the dashboard, and the log names the probable cause instead of leaving a
40 MB Gradle log to read: disk full, missing SDK platform, JDK/Gradle
mismatch, unaccepted licences, a Gradle daemon killed by the kernel (with the
machine's RAM, swap and oom-kill count, because a memory cause is proven and
2026-08-19 05:57:22 -04:00
not assumed), or too many asset files for one APK. The heavy output goes to
`~/erplibre-mobile-build.log` inside the VM so the install log stays readable.
That last cause no longer stops the build. The mobile repo bundles the manifest
repositories into its assets — 122 684 files, for 337 that are the application
— and an APK is a ZIP, capped at 65535 entries: `Too many zip entries 123678`.
The build therefore points `ERPLIBRE_MANIFEST_PATH`, the lever that repo
documents, at an empty manifest, and the plugin says so: `0 repos`. Measured:
`dist` drops from 123 019 files to 336, and the APK comes out at 59 MB with
2 472 entries. Set the variable yourself and the repositories come back — the
default is a stopgap until they fit under the ZIP ceiling.
[ADD] todo qemu: build and test the mobile app, and fail the VM if it breaks A VM was declared ready without anything proving it. The mobile option now adds the repository to the manifest (additive, so it rides along an Odoo 18 install), runs the mobile repository's own install-android.sh — licences accepted included — then npm ci, vite build, cap sync, gradlew assembleDebug and npm test. A failure fails the VM: the exit code reaches the dashboard. Two gaps in that upstream installer had to be filled: unzip and wget, which no cloud image ships, and the SDK platform — it installs android-34 while variables.gradle asks for compileSdk 36, so the number is read from the file rather than frozen. Gradle writes tens of megabytes and hundreds of harmless lines carrying the word error: that output goes to a file of its own, and the log gets the named cause instead. --- FR --- Une VM était déclarée prête sans que rien ne le prouve. L'option mobile ajoute maintenant le dépôt au manifeste (additif, donc il accompagne une installation Odoo 18), lance l'install-android.sh du dépôt mobile lui-même — licences acceptées comprises —, puis npm ci, vite build, cap sync, gradlew assembleDebug et npm test. Un échec fait échouer la VM : le code de sortie remonte au tableau de bord. Deux manques de cet installateur amont ont dû être comblés : unzip et wget, qu'aucune image cloud ne livre, et la plateforme SDK — il pose android-34 quand variables.gradle réclame compileSdk 36, d'où le chiffre lu dans le fichier plutôt que figé. Gradle écrit des dizaines de mégaoctets et des centaines de lignes anodines portant le mot error : cette sortie part dans un fichier à part, et le journal reçoit la cause nommée. Assisted-by: Claude Opus 5
2026-08-18 00:50:08 -04:00
It is bounded to apt-based distributions, because that upstream installer
starts with `sudo apt install openjdk-17-jdk`. It requires no Android Studio
— a plain server VM builds the APK — and when Android Studio is also ticked
they share one SDK through `ANDROID_HOME`. Without Android, the same app runs
in a browser: `npm start`.
A fifth, **Android emulator (Pixel)**, creates an AVD. Drive it from the
QEMU menu, *Android emulator (start, tunnel, scrcpy)*: it starts the emulator
without a window and hands you the adb tunnel and the scrcpy command. Prefer
that to a window over X11 — scrcpy receives H.264 encoded by the device, where
`ssh -X` ships every frame as raw pixels in software rendering. If you do want
the window, the path must be absolute, because `ssh host 'command'` reads
neither `~/.profile` nor `~/.bashrc`:
`ssh -XC erplibre@<ip> '$HOME/android/emulator/emulator -avd erplibre -no-audio'`.
It needs no desktop in the VM, but it does need KVM inside the guest, so
nested virtualisation on the host; the log says so when `/dev/kvm` is missing.
The device is not frozen: the SDK is asked for its profiles and the newest
plain Pixel with the smallest screen wins (no Pro, XL, Fold or tablet).
Rendering is `swangle` in the AVD's own `config.ini` — `auto` would open a
black screen, and `swiftshader_indirect` no longer exists, the emulator
answering `Selected GPU option ... is not valid`.
[ADD] todo qemu: add an Android emulator, and fix what the real run exposed Running it on a VM was the only way to find these. install_os never installed python3-venv, so .venv.erplibre was born crippled — bin/python but no pip, no activate — and everything downstream failed on "No module named git"; one line of dependency fixes it. Capacitor 8 needs a JDK 21 where the mobile repository's installer puts 17, and Gradle must RUN on it. That installer is not idempotent either, so it is replayed only when something is missing. The new emulator option creates an AVD from the SDK's own device list — the newest plain Pixel, smallest screen — with software rendering written into its config so ssh -X does not open a black screen, and adds the user to the kvm group, without which it refuses to start. Checked on a VM: boot completed in 10 s, adb sees emulator-5554, Android 16 x86_64, no KVM refusal. Vitest: 1938 tests pass. The APK still fails, upstream: sentencepiece builds protoc for the target then runs it on the host. --- FR --- Seule l'exécution sur une VM pouvait trouver ceci. install_os n'installait pas python3-venv, si bien que .venv.erplibre naissait infirme — bin/python mais ni pip ni activate — et tout ce qui en dépend tombait sur « No module named git » ; une ligne de dépendance suffit. Capacitor 8 réclame un JDK 21 là où l'installateur du dépôt mobile pose un 17, et Gradle doit TOURNER dessus. Cet installateur n'est pas idempotent non plus : il n'est rejoué que s'il manque quelque chose. La nouvelle option crée un AVD depuis la liste de profils du SDK — le Pixel simple le plus récent, plus petit écran —, écrit le rendu logiciel dans sa configuration pour qu'ssh -X n'ouvre pas un écran noir, et ajoute l'utilisateur au groupe kvm, sans quoi il refuse de démarrer. Vérifié sur une VM : boot en 10 s, adb voit emulator-5554, Android 16 x86_64, aucun refus de KVM. Vitest : 1938 tests passent. L'APK échoue encore, en amont : sentencepiece bâtit protoc pour la cible puis l'exécute sur l'hôte. Assisted-by: Claude Opus 5
2026-08-18 02:17:08 -04:00
[ADD] todo qemu: build and test the mobile app, and fail the VM if it breaks A VM was declared ready without anything proving it. The mobile option now adds the repository to the manifest (additive, so it rides along an Odoo 18 install), runs the mobile repository's own install-android.sh — licences accepted included — then npm ci, vite build, cap sync, gradlew assembleDebug and npm test. A failure fails the VM: the exit code reaches the dashboard. Two gaps in that upstream installer had to be filled: unzip and wget, which no cloud image ships, and the SDK platform — it installs android-34 while variables.gradle asks for compileSdk 36, so the number is read from the file rather than frozen. Gradle writes tens of megabytes and hundreds of harmless lines carrying the word error: that output goes to a file of its own, and the log gets the named cause instead. --- FR --- Une VM était déclarée prête sans que rien ne le prouve. L'option mobile ajoute maintenant le dépôt au manifeste (additif, donc il accompagne une installation Odoo 18), lance l'install-android.sh du dépôt mobile lui-même — licences acceptées comprises —, puis npm ci, vite build, cap sync, gradlew assembleDebug et npm test. Un échec fait échouer la VM : le code de sortie remonte au tableau de bord. Deux manques de cet installateur amont ont dû être comblés : unzip et wget, qu'aucune image cloud ne livre, et la plateforme SDK — il pose android-34 quand variables.gradle réclame compileSdk 36, d'où le chiffre lu dans le fichier plutôt que figé. Gradle écrit des dizaines de mégaoctets et des centaines de lignes anodines portant le mot error : cette sortie part dans un fichier à part, et le journal reçoit la cause nommée. Assisted-by: Claude Opus 5
2026-08-18 00:50:08 -04:00
Each tool is filtered per VM — by architecture, desktop flavour and package
family — and its disk cost is added to the plan before anything is created.
[ADD] todo qemu: offer PyCharm, Android Studio and GNOME extensions A graphical VM was a desktop and nothing else: every developer tool had to be installed by hand afterwards. A check list now carries them, filtered per machine — Android Studio is x86_64 only, Google publishes no Linux aarch64 build — and their disk cost reaches the plan before any qcow2 is created. They are installed BEFORE the clone: PyCharm writes the .idea/ of the repository, and the install that follows is what runs pycharm_configuration.py, through update_env_version.pycharm_update(). Its launcher is named studio, which is enough to conclude the install failed; it now answers to android-studio too. GNOME extensions come from the site by UUID, per running Shell: the same endpoint serves gTile v59 for GNOME 46 and v62 for 48. Checked with stubs: every tool can fail and the install's exit code still wins, 45 tests. --- FR --- Une VM graphique n'était qu'un bureau : chaque outil de développement restait à poser à la main. Une liste à cocher les porte, filtrés machine par machine — Android Studio n'existe qu'en x86_64, Google ne publiant aucune archive Linux aarch64 — et leur place disque atteint le plan avant qu'un seul qcow2 ne soit créé. Ils sont posés AVANT le clone : PyCharm écrit le .idea/ du dépôt, et c'est l'installation qui suit qui lance pycharm_configuration.py, via update_env_version.pycharm_update(). Son lanceur s'appelle studio, ce qui suffit à conclure à un échec ; il répond désormais aussi à android-studio. Les extensions GNOME viennent du site par UUID, selon le Shell qui tourne : le même point d'entrée sert gTile v59 pour GNOME 46 et v62 pour 48. Vérifié avec des leurres : chaque outil peut échouer sans que le code de sortie de l'installation ne change, 45 tests. Assisted-by: Claude Opus 5
2026-08-17 22:57:56 -04:00
## Main options
- `--distro` — `ubuntu` (default), `debian` or `fedora`.
- `--version` — release for the distro (default: the distro's default).
- `--list-images` — print all distros/versions and their specs, then exit.
- `--image-dir` — image cache directory (default `/var/lib/libvirt/images/iso`).
- `--download-only` — download the image then exit (no VM).
- `--name` — VM name (required for deployment).
- `--memory`, `--vcpus`, `--disk-size` — VM sizing. When omitted, `--memory`
and `--disk-size` default to the **minimum required by the chosen version**
(libosinfo values, see `--list-images`: Ubuntu 24.04+ → 3072 MB/20G, Debian
→ 1024 MB/10G, Fedora → 2048 MB/15G); `--vcpus` defaults to 2.
- `--ssh-key`, `--ask-password`, `--password-hash` — authentication.
- `-y` / `--assume-yes` — auto-accept dependency installation.
- `--no-install-deps` — never auto-install dependencies.
- `--dry-run` — show the commands without executing anything.
- `--force` — overwrite the existing working qcow2 disk.
[ADD] todo qemu: offer PyCharm, Android Studio and GNOME extensions A graphical VM was a desktop and nothing else: every developer tool had to be installed by hand afterwards. A check list now carries them, filtered per machine — Android Studio is x86_64 only, Google publishes no Linux aarch64 build — and their disk cost reaches the plan before any qcow2 is created. They are installed BEFORE the clone: PyCharm writes the .idea/ of the repository, and the install that follows is what runs pycharm_configuration.py, through update_env_version.pycharm_update(). Its launcher is named studio, which is enough to conclude the install failed; it now answers to android-studio too. GNOME extensions come from the site by UUID, per running Shell: the same endpoint serves gTile v59 for GNOME 46 and v62 for 48. Checked with stubs: every tool can fail and the install's exit code still wins, 45 tests. --- FR --- Une VM graphique n'était qu'un bureau : chaque outil de développement restait à poser à la main. Une liste à cocher les porte, filtrés machine par machine — Android Studio n'existe qu'en x86_64, Google ne publiant aucune archive Linux aarch64 — et leur place disque atteint le plan avant qu'un seul qcow2 ne soit créé. Ils sont posés AVANT le clone : PyCharm écrit le .idea/ du dépôt, et c'est l'installation qui suit qui lance pycharm_configuration.py, via update_env_version.pycharm_update(). Son lanceur s'appelle studio, ce qui suffit à conclure à un échec ; il répond désormais aussi à android-studio. Les extensions GNOME viennent du site par UUID, selon le Shell qui tourne : le même point d'entrée sert gTile v59 pour GNOME 46 et v62 pour 48. Vérifié avec des leurres : chaque outil peut échouer sans que le code de sortie de l'installation ne change, 45 tests. Assisted-by: Claude Opus 5
2026-08-17 22:57:56 -04:00
- `--lang` — language of the SSH login guide, `fr` (default) or `en`. The
TODO menu passes its own language.
- `--erplibre-dir` — where ERPLibre will live in the VM
(`~/git/erplibre`, or `/opt/erplibre` in production). Adds the ERPLibre
section to the login guide; omitted, that section is left out.
- `--erplibre-make` — the make target that installed the VM
(e.g. `install_odoo_18`), shown in the guide as the way to update it.
- `--no-git-identity` — do not copy the host's `user.name`, `user.email`
and `core.editor` into the VM's `~/.gitconfig`.
Run `./script/qemu/deploy_qemu.py --help` for the full list.
[ADD] todo qemu: offer PyCharm, Android Studio and GNOME extensions A graphical VM was a desktop and nothing else: every developer tool had to be installed by hand afterwards. A check list now carries them, filtered per machine — Android Studio is x86_64 only, Google publishes no Linux aarch64 build — and their disk cost reaches the plan before any qcow2 is created. They are installed BEFORE the clone: PyCharm writes the .idea/ of the repository, and the install that follows is what runs pycharm_configuration.py, through update_env_version.pycharm_update(). Its launcher is named studio, which is enough to conclude the install failed; it now answers to android-studio too. GNOME extensions come from the site by UUID, per running Shell: the same endpoint serves gTile v59 for GNOME 46 and v62 for 48. Checked with stubs: every tool can fail and the install's exit code still wins, 45 tests. --- FR --- Une VM graphique n'était qu'un bureau : chaque outil de développement restait à poser à la main. Une liste à cocher les porte, filtrés machine par machine — Android Studio n'existe qu'en x86_64, Google ne publiant aucune archive Linux aarch64 — et leur place disque atteint le plan avant qu'un seul qcow2 ne soit créé. Ils sont posés AVANT le clone : PyCharm écrit le .idea/ du dépôt, et c'est l'installation qui suit qui lance pycharm_configuration.py, via update_env_version.pycharm_update(). Son lanceur s'appelle studio, ce qui suffit à conclure à un échec ; il répond désormais aussi à android-studio. Les extensions GNOME viennent du site par UUID, selon le Shell qui tourne : le même point d'entrée sert gTile v59 pour GNOME 46 et v62 pour 48. Vérifié avec des leurres : chaque outil peut échouer sans que le code de sortie de l'installation ne change, 45 tests. Assisted-by: Claude Opus 5
2026-08-17 22:57:56 -04:00
## Login guide (`/etc/motd`)
Every VM greets you, at each interactive SSH login, with the commands of
**its own** distribution — `apt`, `dnf`, `zypper` or `pacman` — plus the
ERPLibre ones (edit the server, restart it, update modules, update Odoo,
inspect the instance, open the TODO menu). It is written by cloud-init, so
it is there from the first boot: before ERPLibre is installed, and still
there if that installation fails, which is exactly when you log in by hand.
`--dry-run` prints the generated guide along with the rest of the user-data.
The guide is not shown to `ssh host 'command'`, so it never pollutes an
installation log.
The host's git identity travels with it, into the VM's `~/.gitconfig`: a
commit made in the VM then carries your name instead of
`erplibre@<vm-name>`. The editor follows the same route — `core.editor`, the
`config.conf` line of the guide, and the package installed in the VM all
come from one table, so the guide never names a command the VM does not
have.
## Managing VMs
List, stop and remove VMs (the qcow2 disk under `/var/lib/libvirt/images`
is kept unless you delete it):
```bash
sudo virsh list --all # toutes les VM et leur état / all VMs and state
sudo virsh shutdown <nom-vm> # arrêt propre ACPI / graceful shutdown
sudo virsh destroy <nom-vm> # arrêt forcé / force off (pull the plug)
sudo virsh undefine <nom-vm> # supprime la définition / remove definition
sudo virsh domifaddr <nom-vm> # adresse IP de la VM / VM IP address
```
`destroy` only powers the VM off (disk kept); `undefine` removes its
definition. To fully recreate a VM with the same name, `destroy` + `undefine`
it first, or redeploy with `--force`.
## SSH access from another machine (ProxyJump)
With the default NAT network the VM is reachable **only from the KVM host**.
To reach it from another machine **without changing the network**, use the
host as a jump host (it already reaches the VM). Get the VM IP with
`sudo virsh domifaddr <nom-vm>`, then from the other machine:
```bash
# Rebond SSH vers la VM / jump through the KVM host
ssh -J user@<ip-hote> erplibre@<ip-vm>
# Tunnel d'un service, ex. Odoo 8069 / tunnel a service, then http://localhost:8069
ssh -L 8069:<ip-vm>:8069 user@<ip-hote>
```
To make it permanent, add this to `~/.ssh/config` on the other machine (then
just `ssh myvm`):
```text
Host myvm
HostName <ip-vm> # ex. 192.168.122.50 (reseau NAT)
User erplibre
ProxyJump user@<ip-hote> # IP LAN de l'hote KVM
```
This works over Wi-Fi and needs no VM shutdown — the simplest option for
personal access. Prefer a bridge (below) if the VM must be a full server
exposed on the LAN.
## QEMU inside QEMU (nested) & exposing the VM via a bridge
If the KVM host is **itself a VM** (QEMU-in-QEMU), the deployment works only
when **nested virtualization** is enabled on the outer/physical host and the
middle VM uses CPU mode `host-passthrough`. Check from inside the KVM host
(the first command must be non-empty):
```bash
grep -E -o '(vmx|svm)' /proc/cpuinfo | sort -u # extensions visibles / visible
# Sur l'hote PHYSIQUE / on the PHYSICAL host:
cat /sys/module/kvm_intel/parameters/nested # Intel -> Y/1
cat /sys/module/kvm_amd/parameters/nested # AMD -> Y/1
```
To enable nesting on the physical host (Intel shown; use `kvm_amd` on AMD),
then recreate the middle VM with `host-passthrough`:
```bash
echo "options kvm_intel nested=1" | sudo tee /etc/modprobe.d/kvm-nested.conf
sudo modprobe -r kvm_intel && sudo modprobe kvm_intel # ou / or reboot
```
On **s390x and arm64** the parameter lives on the `kvm` module itself, not on
`kvm_intel` / `kvm_amd` — and `/sys/module/kvm/parameters/nested` does not even
exist on x86. Reading the wrong file returns a reassuring `0` that commands
nothing:
```bash
echo "options kvm nested=1" | sudo tee /etc/modprobe.d/kvm-nested.conf
sudo modprobe -r kvm && sudo modprobe kvm # ou / or reboot
```
`nested` on a machine means « let MY guests run VMs ». To accelerate a VM
created on host H, the setting belongs to the hypervisor **above** H, not to H
itself. The one command that settles it, run on H:
```bash
ls -l /dev/kvm # absent -> pas d'imbrication, tout sera émulé
```
Measured on an s390x host that was itself a KVM guest without nesting:
`/dev/kvm` absent, `virsh dumpxml` showing `<domain type='qemu'>`, and a
7 min 30 boot instead of well under a minute. `systemd-detect-virt` inside the
VM is **not** proof of acceleration — on s390x, QEMU fabricates the STSI answer
and reports `kvm` even under TCG. Only `<domain type=…>` on the host is
conclusive.
If nesting is unavailable, QEMU still runs via software emulation (TCG) — it
works but is slow.
```yaml
# /etc/netplan/01-br0.yaml
network:
version: 2
renderer: networkd
ethernets:
enp3s0: {dhcp4: no, dhcp6: no}
bridges:
br0:
interfaces: [enp3s0]
dhcp4: yes
parameters: {stp: false, forward-delay: 0}
```
Apply safely (auto-reverts if you lose the connection) and verify — or use
NetworkManager (Ubuntu desktop):
```bash
# netplan
sudo netplan try && sudo netplan apply
ip addr show br0 # br0 porte l'IP du LAN / br0 holds the LAN IP
# NetworkManager (alternative)
nmcli con add type bridge ifname br0 con-name br0
nmcli con add type ethernet ifname enp3s0 master br0 con-name br0-port
nmcli con modify br0 ipv4.method auto
nmcli con down "Wired connection 1" ; nmcli con up br0
```
Then attach the VM to the bridge — **either at creation**:
```bash
sudo ./script/qemu/deploy_qemu.py --name <nom-vm> --version 24.04 \
--ssh-key ~/.ssh/id_ed25519.pub --network bridge=br0,model=virtio -y --force
```
**or by editing a VM already created**: stop it, replace its `<interface>`
block (`type='network'` / `<source network='default'/>` → `type='bridge'` /
`<source bridge='br0'/>`), then start it again:
```bash
sudo virsh shutdown <nom-vm>
sudo virsh edit <nom-vm> # mettre l'interface en bridge=br0
sudo virsh start <nom-vm>
sudo virsh domifaddr <nom-vm> # nouvelle IP LAN / new LAN IP
```
The VM now gets a LAN IP from your router, reachable by other machines. From
the Internet you additionally need a port-forward on your router (or a VPN);
in a nested setup the outer host must also forward/expose the middle VM.