[ADD] tui qemu: graphical VMs, with a GNOME desktop

VMs could only be servers. A server-or-graphical choice joins both
interfaces, installing GNOME along with remote access to it.

Packages come from the remote command, not cloud-init: their 1 to 2 GB
would stretch an already long boot there while leaving no trace in the
monitoring, and group installs do not go through it. The desktop
therefore does not depend on ERPLibre -- a VM may be wanted graphical
and bare.

Each distribution has its own names, taken from the source: Arch has no
xrdp in its official repositories and takes TigerVNC. The SPICE display
is set only on amd64 and arm64; s390x does expose virtio-gpu-ccw, but
nothing guarantees its kernel's DRM driver, whereas remote desktop works
everywhere.

--- FR ---

Les VM ne pouvaient être que des serveurs. Un choix serveur ou graphique
s'ajoute aux deux interfaces, et pose GNOME avec son accès distant.

Les paquets viennent de la commande distante, pas de cloud-init : leurs
1 à 2 Go y allongeraient un démarrage déjà long sans laisser de trace
dans le suivi, et les installations par groupe n'y passent pas. Le bureau
ne dépend donc pas d'ERPLibre — une VM peut être voulue graphique et nue.

Chaque distribution a ses noms, relevés à la source : Arch n'a pas xrdp
dans ses dépôts officiels et prend TigerVNC. L'écran virtuel SPICE n'est
posé que sur amd64 et arm64 ; s390x expose bien virtio-gpu-ccw, mais rien
ne garantit le pilote DRM de son noyau, alors que le bureau distant, lui,
marche partout.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-08-11 09:49:17 -04:00
parent 891d412aa2
commit bace74b32b
4 changed files with 301 additions and 73 deletions

View file

@ -1631,6 +1631,22 @@ def virt_install(
# s390x n'a pas de port série ISA : la console est SCLP (ttysclp0), et non
# ttyS0. Ailleurs (x86/arm64), console série classique.
console_target = "sclp" if args.arch == "s390x" else "serial"
# Écran virtuel pour une VM graphique. s390x en est écarté : QEMU y expose
# bien « virtio-gpu-ccw », mais rien ne garantit que le noyau s390x de la
# distribution embarque le pilote DRM virtio-gpu — la VM démarrerait alors
# sur un écran noir. Le bureau distant, lui, marche partout : c'est la voie
# retenue pour cette architecture, et le message le dit.
graphics = args.graphics
video = []
if args.desktop and graphics == "none":
if args.arch == "s390x":
print(
"\n s390x : pas d'écran virtuel (pilote DRM non garanti)."
"\n Le bureau sera accessible à distance, par le réseau."
)
else:
graphics = "spice,listen=none"
video = ["--video", "virtio"]
cmd = [
"virt-install",
# Sans --connect, un utilisateur non root vise qemu:///session : le
@ -1660,7 +1676,7 @@ def virt_install(
"--network",
args.network,
"--graphics",
args.graphics,
graphics,
"--console",
f"pty,target_type={console_target}",
# Canal virtio de l'agent invité (org.qemu.guest_agent.0) : permet à
@ -1669,6 +1685,7 @@ def virt_install(
"--channel",
"unix,target.type=virtio,target.name=org.qemu.guest_agent.0",
]
cmd += video
if args.arch == "s390x":
# s390x (IBM Z) : machine s390-ccw-virtio, amorçage IPL/zipl depuis le
# disque (ni BIOS ni UEFI/OVMF -> aucun --boot).
@ -1883,6 +1900,13 @@ def build_parser() -> argparse.ArgumentParser:
default="none",
help="Argument --graphics (défaut : none).",
)
g_vm.add_argument(
"--desktop",
action="store_true",
help="VM graphique : écran virtuel SPICE là où l'architecture le "
"permet. Les paquets GNOME sont posés par la commande d'installation, "
"pas ici.",
)
g_vm.add_argument(
"--osinfo", help="Force la valeur --osinfo (sinon déduite)."
)

View file

@ -177,6 +177,7 @@ def build_spec(vms, domains, form):
"existing": [vm["name"] for vm in vms if vm["name"] in known],
"ssh_key": form["ssh_key"],
"timezone": form.get("timezone", ""),
"desktop": form.get("desktop", False),
"install": form["install"],
"add_ssh_config": form["add_ssh_config"],
"parallelism": form["parallelism"],
@ -241,6 +242,7 @@ def run_deploy_form(ctx, run_app: bool = True):
free_ram = ctx.get("free_ram") or 0
base_vcpus = ctx.get("base_vcpus") or 2
extra_disk = ctx.get("extra_disk_gb") or 0
desktop_disk = ctx.get("desktop_disk_gb") or 0
defaults = ctx.get("defaults") or {}
result = {"spec": None}
@ -443,6 +445,19 @@ def run_deploy_form(ctx, run_app: bool = True):
classes="freeval",
disabled=True,
)
# Serveur par défaut : c'est ce que sert une image cloud,
# et GNOME ajoute une à deux heures sur une architecture
# émulée. Le plan annonce le surcoût disque.
yield Static(t("VM type:"), classes="grouptitle")
with RadioSet(id="f_type"):
yield RadioButton(
t("Server (no graphical interface)"),
value=not defaults.get("desktop", False),
)
yield RadioButton(
t("Graphical (server + GNOME desktop)"),
value=defaults.get("desktop", False),
)
yield Static("ERPLibre", classes="grouptitle")
yield Checkbox(
t("Install ERPLibre"),
@ -574,17 +589,18 @@ def run_deploy_form(ctx, run_app: bool = True):
self.custom,
self.overrides,
)
self.rows = plan_rows(
self.vms,
domains,
(
extra_disk
if self.query_one("#f_install", Checkbox).value
else 0
),
)
# ERPLibre et GNOME pèsent chacun sur le disque, et se cumulent.
grow = 0
if self.query_one("#f_install", Checkbox).value:
grow += extra_disk
if self._desktop():
grow += desktop_disk
self.rows = plan_rows(self.vms, domains, grow)
self._render_plan()
def _desktop(self):
return self.query_one("#f_type", RadioSet).pressed_index == 1
def _render_plan(self):
table = self.query_one("#plan", DataTable)
table.clear()
@ -631,6 +647,8 @@ def run_deploy_form(ctx, run_app: bool = True):
if event.radio_set.id == "f_arch":
self.arch = arches[event.radio_set.pressed_index]
self._reload_catalog()
elif event.radio_set.id == "f_type":
self._recompute() # le disque annonce inclut GNOME
elif event.radio_set.id == "f_profile":
index = event.radio_set.pressed_index
self.profile = "custom" if index == 4 else str(index + 1)
@ -742,6 +760,7 @@ def run_deploy_form(ctx, run_app: bool = True):
"timezone": self.query_one("#f_tz", Input).value.strip()
or ctx.get("timezone")
or "",
"desktop": self._desktop(),
"install": install,
"add_ssh_config": self.query_one("#f_sshcfg", Checkbox).value,
# Une exécution par installation : le nombre de VM retenues

View file

@ -4303,11 +4303,158 @@ class TODO:
"sudo systemctl enable --now erplibre.service"
)
def _qemu_erplibre_remote_cmd(self, branch, final_cmd=None, prod=False):
"""Script d'installation ERPLibre exécuté DANS la VM (curl/git/make
puis clone + `final_cmd`). `final_cmd` par défaut : install_os +
install_odoo_18. `prod` : installe dans /opt/erplibre (au lieu de
~/git/erplibre) + service SELinux confiné."""
# GNOME et son accès distant, par gestionnaire de paquets. Une seule
# source pour la TUI et la CLI. Les noms ont été relevés distribution par
# distribution, pas déduits : Arch n'a PAS xrdp dans ses dépôts officiels
# (AUR seulement) et prend TigerVNC, avec un port et un client différents.
_QEMU_DESKTOP = {
"apt": {
"packages": "gnome-core xrdp dbus-x11",
"groups": "",
"remote": "xrdp",
"port": 3389,
"client": "RDP",
},
"dnf": {
"packages": "xrdp",
"groups": "gnome-desktop",
"remote": "xrdp",
"port": 3389,
"client": "RDP",
},
"pacman": {
"packages": "gdm tigervnc",
"groups": "gnome",
"remote": "",
"port": 5901,
"client": "VNC",
},
}
# Place que prend un bureau complet, annoncée dans le plan : sur une image
# cloud de 40 G, l'oublier remplit le disque en pleine installation.
QEMU_DESKTOP_EXTRA_DISK_GB = 6
@staticmethod
def _qemu_cloud_init_wait():
"""Attend la fin de cloud-init, qui tient le verrou apt/dnf/pacman
pendant sa phase « paquets ».
L'attente dure jusqu'à 15 min et n'écrivait RIEN : sur une architecture
émulée, le log restait muet un quart d'heure juste après avoir annoncé
le début de l'installation, ce qui se lit comme un blocage. Deux lignes
l'encadrent, et le « status » final dit si elle a abouti ou expiré."""
return (
"if command -v cloud-init >/dev/null 2>&1; then "
'echo "== '
+ t("Waiting for cloud-init to finish (up to 15 min)")
+ ' =="; '
"sudo timeout 900 cloud-init status --wait >/dev/null 2>&1 "
"|| true; "
+ f'echo " {t("cloud-init:")} $(cloud-init status 2>/dev/null '
'| head -1)"; '
"fi; "
)
@staticmethod
def _qemu_no_auto_upgrade(prod):
"""Coupe les mises à jour automatiques sur une VM de DÉVELOPPEMENT.
Vécu sur erplibre-ubuntu-2404 : unattended-upgrades s'est déclenché en
pleine migration Odoo 12->13 et a redémarré le cluster PostgreSQL
(« received fast shutdown request » x3) -> OpenUpgrade a perdu sa
connexion et la base intermédiaire est restée à moitié migrée. Effet
secondaire bienvenu : les timers apt-daily ne tiennent plus le verrou
apt pendant l'installation. En PROD on ne touche à rien : les
correctifs de sécurité automatiques doivent rester actifs."""
if prod:
return ""
return (
"if command -v apt-get >/dev/null 2>&1; then "
"sudo systemctl disable --now unattended-upgrades.service "
"apt-daily.timer apt-daily-upgrade.timer "
">/dev/null 2>&1 || true; "
'printf \'APT::Periodic::Update-Package-Lists "0";\\n'
'APT::Periodic::Unattended-Upgrade "0";\\n\' '
"| sudo tee /etc/apt/apt.conf.d/99-erplibre-no-auto-upgrade "
">/dev/null; "
"fi; "
"if command -v dnf >/dev/null 2>&1; then "
"sudo systemctl disable --now dnf-automatic.timer "
"dnf-automatic-install.timer >/dev/null 2>&1 || true; "
"fi; "
# snapd : 57 s sur le CHEMIN CRITIQUE du démarrage, mesurés par
# « systemd-analyze critical-chain » sur une VM s390x —
# multi-user.target attend snapd.seeded. Aucune VM ERPLibre
# n'installe de snap : c'est du temps payé pour rien, à chaque
# démarrage. On désactive plutôt que désinstaller, pour rester
# réversible d'un « systemctl enable ».
"sudo systemctl disable --now snapd.seeded.service "
"snapd.service snapd.socket snapd.apparmor.service "
">/dev/null 2>&1 || true; "
)
def _qemu_desktop_remote_cmd(self):
"""Bloc shell installant GNOME + son accès distant, quelle que soit la
distribution. Même aiguillage que l'installation ERPLibre, et même
traitement du verrou apt : cette étape passe par la commande distante
et non par cloud-init, où ses 1 à 2 Go allongeraient un démarrage déjà
long sans laisser la moindre trace dans le suivi."""
apt = self._QEMU_DESKTOP["apt"]
dnf = self._QEMU_DESKTOP["dnf"]
pac = self._QEMU_DESKTOP["pacman"]
return (
f'echo "== {t("Installing the GNOME desktop (long)")} =="; '
"if command -v apt-get >/dev/null 2>&1; then "
"n=0; until sudo apt-get -o DPkg::Lock::Timeout=120 update -qq; do "
"n=$((n+1)); [ $n -ge 30 ] && break; sleep 10; done; "
"sudo DEBIAN_FRONTEND=noninteractive "
"apt-get -o DPkg::Lock::Timeout=600 install -y "
f"{apt['packages']}; "
"elif command -v dnf >/dev/null 2>&1; then "
f"sudo dnf -y group install {dnf['groups']}; "
f"sudo dnf install -y {dnf['packages']}; "
"elif command -v pacman >/dev/null 2>&1; then "
"pgrep -x pacman >/dev/null 2>&1 "
"|| sudo rm -f /var/lib/pacman/db.lck; "
f"sudo pacman -S --needed --noconfirm {pac['groups']} "
f"{pac['packages']}; "
"else echo 'Gestionnaire de paquets inconnu'; exit 1; fi; "
# Le bureau ne sert à rien s'il ne démarre pas tout seul : les
# images cloud démarrent en multi-user.target.
"sudo systemctl set-default graphical.target || true; "
"sudo systemctl enable gdm >/dev/null 2>&1 || true; "
# xrdp là où il existe ; sur Arch c'est TigerVNC, qui se configure
# par utilisateur et n'a pas de service à activer d'office.
"if command -v xrdp >/dev/null 2>&1; then "
"sudo systemctl enable --now xrdp >/dev/null 2>&1 || true; "
f'echo " {t("Remote desktop:")} RDP 3389"; '
"elif command -v vncserver >/dev/null 2>&1; then "
f'echo " {t("Remote desktop:")} VNC 5901 '
'(vncpasswd puis vncserver :1)"; fi; '
)
def _qemu_erplibre_remote_cmd(
self, branch, final_cmd=None, prod=False, desktop=False
):
"""Script exécuté DANS la VM. `branch` à None n'installe QUE le bureau
— le choix graphique ne dépend pas d'ERPLibre, et une VM peut être
voulue en bureau seul.
`final_cmd` par défaut : install_os + install_odoo_18. `prod` :
installe dans /opt/erplibre (au lieu de ~/git/erplibre) + service
SELinux confiné. `desktop` : ajoute GNOME et son accès distant."""
if not branch:
# Bureau seul : ni clone ni make, mais on garde le prologue —
# attente de cloud-init et coupure des mises à jour automatiques,
# sans quoi le verrou apt ferait échouer l'installation du bureau.
if not desktop:
return "true"
return (
"set -e; "
+ self._qemu_cloud_init_wait()
+ self._qemu_no_auto_upgrade(prod)
+ self._qemu_desktop_remote_cmd()
)
if not final_cmd:
final_cmd = f"make install_os && make {self.ERPLIBRE_ODOO_TARGET}"
# Profils AVEC Odoo (install_odoo*) uniquement : après l'install, on
@ -4331,56 +4478,16 @@ class TODO:
# secondaire bienvenu : les timers apt-daily ne tiennent plus le verrou
# apt pendant l'installation. En PROD on ne touche à rien : les
# correctifs de sécurité automatiques doivent rester actifs.
no_auto_upgrade = ""
if not prod:
no_auto_upgrade = (
"if command -v apt-get >/dev/null 2>&1; then "
"sudo systemctl disable --now unattended-upgrades.service "
"apt-daily.timer apt-daily-upgrade.timer "
">/dev/null 2>&1 || true; "
'printf \'APT::Periodic::Update-Package-Lists "0";\\n'
'APT::Periodic::Unattended-Upgrade "0";\\n\' '
"| sudo tee /etc/apt/apt.conf.d/99-erplibre-no-auto-upgrade "
">/dev/null; "
"fi; "
"if command -v dnf >/dev/null 2>&1; then "
"sudo systemctl disable --now dnf-automatic.timer "
"dnf-automatic-install.timer >/dev/null 2>&1 || true; "
"fi; "
# snapd : 57 s sur le CHEMIN CRITIQUE du démarrage, mesurés par
# « systemd-analyze critical-chain » sur une VM s390x —
# multi-user.target attend snapd.seeded. Aucune VM ERPLibre
# n'installe de snap : c'est du temps payé pour rien, à chaque
# démarrage. On désactive plutôt que désinstaller, pour rester
# réversible d'un « systemctl enable ».
"sudo systemctl disable --now snapd.seeded.service "
"snapd.service snapd.socket snapd.apparmor.service "
">/dev/null 2>&1 || true; "
)
no_auto_upgrade = self._qemu_no_auto_upgrade(prod)
return (
"set -e; "
# Attendre la FIN de cloud-init : pendant sa phase « paquets » il
# tient le verrou apt/dnf/pacman -> sinon « unable to lock
# database » (Arch) / « Could not get lock » (apt). timeout pour ne
# pas bloquer indéfiniment si cloud-init traîne.
#
# Cette attente dure jusqu'à 15 min et n'écrivait RIEN : sur une
# architecture émulée, le log restait muet d'un quart d'heure juste
# après avoir annoncé le début de l'installation, ce qui se lit
# comme un blocage. On encadre donc l'attente de deux lignes, et le
# « status » final dit si elle a abouti ou expiré.
"if command -v cloud-init >/dev/null 2>&1; then "
+ 'echo "== '
+ t("Waiting for cloud-init to finish (up to 15 min)")
+ ' =="; '
"sudo timeout 900 cloud-init status --wait >/dev/null 2>&1 "
"|| true; "
+ f'echo " {t("cloud-init:")} $(cloud-init status 2>/dev/null '
'| head -1)"; '
"fi; "
"set -e; " + self._qemu_cloud_init_wait()
# Coupé AVANT les apt-get ci-dessous : sinon apt-daily peut reprendre
# le verrou entre l'attente cloud-init et l'installation.
+ no_auto_upgrade +
+ no_auto_upgrade
# Le bureau d'abord : il repose sur les dépôts de la distribution,
# là où l'installation ERPLibre compile longuement. Un échec ici se
# voit donc tôt plutôt qu'après une heure.
+ (self._qemu_desktop_remote_cmd() if desktop else "") +
# Outils d'amorçage (absents des images cloud minimales) : curl,
# git, make. Chaque branche RAFRAÎCHIT d'abord les dépôts pour que
# la VM soit la plus rapide possible (miroirs à jour / les plus
@ -4453,7 +4560,13 @@ class TODO:
)
def _qemu_install_erplibre_monitored(
self, names, branch, ip_map=None, final_cmd=None, prod=False
self,
names,
branch,
ip_map=None,
final_cmd=None,
prod=False,
desktop=False,
):
"""Lance l'install ERPLibre en parallèle DÉTACHÉE sur les VM et ouvre
le dashboard Textual. Quitter le dashboard n'arrête pas les installs.
@ -4465,7 +4578,9 @@ class TODO:
run_monitor,
)
remote = self._qemu_erplibre_remote_cmd(branch, final_cmd, prod)
remote = self._qemu_erplibre_remote_cmd(
branch, final_cmd, prod, desktop
)
try:
mod = self._qemu_import_module()
except Exception:
@ -4521,7 +4636,14 @@ class TODO:
print(f" {t('Read the logs:')} tail -n +1 {logdir}/*.log")
def _qemu_install_erplibre_vm(
self, name, ssh_key, branch, ip=None, final_cmd=None, prod=False
self,
name,
ssh_key,
branch,
ip=None,
final_cmd=None,
prod=False,
desktop=False,
):
"""Clone ERPLibre (branche donnée) dans la VM puis exécute la commande
d'install du profil choisi (streamé). `ip` : IP déjà résolue ;
@ -4542,7 +4664,9 @@ class TODO:
f"{t('SSH not reachable, ERPLibre install skipped.')}"
)
return
remote = self._qemu_erplibre_remote_cmd(branch, final_cmd, prod)
remote = self._qemu_erplibre_remote_cmd(
branch, final_cmd, prod, desktop
)
ssh_opts = (
"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "
"-o ConnectTimeout=15"
@ -4915,6 +5039,11 @@ class TODO:
)
else:
print(f" {t('ERPLibre install:')} {t('no')}")
if spec.get("desktop"):
print(
f" {t('VM type:')} "
f"{t('Graphical (server + GNOME desktop)')}"
)
print(f" {t('SSH key:')} {spec.get('ssh_key') or t('none')}")
cfg = (
t("one entry per VM") if spec["add_ssh_config"] else t("untouched")
@ -4936,6 +5065,7 @@ class TODO:
dry_run,
timezone=None,
locale=None,
desktop=False,
):
"""Construit la commande deploy_qemu.py d'UNE VM (utilisée pour l'aperçu
dry-run ET le déploiement réel)."""
@ -4969,9 +5099,18 @@ class TODO:
parts += ["--timezone", timezone]
if locale:
parts += ["--locale", locale]
if desktop:
parts.append("--desktop")
extra = 0
if branch:
# ERPLibre dépasse le minimum : +5 Go de disque.
bigger = self._parse_disk_gb(disk) + self.ERPLIBRE_EXTRA_DISK_GB
extra += self.ERPLIBRE_EXTRA_DISK_GB
if desktop:
# GNOME et ses dépendances pèsent autant qu'ERPLibre : sans cette
# marge, le disque se remplit en pleine installation du bureau.
extra += self.QEMU_DESKTOP_EXTRA_DISK_GB
if extra:
bigger = self._parse_disk_gb(disk) + extra
parts += ["--disk-size", f"{bigger}G"]
parts.append("--dry-run" if dry_run else "-y")
return parts
@ -4996,6 +5135,7 @@ class TODO:
dry_run=dry_run,
timezone=spec.get("timezone"),
locale=spec.get("locale"),
desktop=bool(spec.get("desktop")),
)
# ---------------------------------------------------------------- #
@ -5230,6 +5370,7 @@ class TODO:
"ram_presets": self._QEMU_RAM_PRESETS,
"disk_presets": self._QEMU_DISK_PRESETS,
"extra_disk_gb": self.ERPLIBRE_EXTRA_DISK_GB,
"desktop_disk_gb": self.QEMU_DESKTOP_EXTRA_DISK_GB,
"defaults": {
"install": True,
"add_ssh_config": True,
@ -5499,6 +5640,16 @@ class TODO:
)
print(f" ⚠ {warn}")
def _qemu_ask_desktop(self):
"""Serveur, ou serveur plus un bureau GNOME.
Serveur par défaut : c'est ce que sert une image cloud, et le bureau
ajoute une à deux heures d'installation sur une architecture émulée."""
print(f"\n{t('VM type:')}")
print(f" [1] {t('Server (no graphical interface)')} *")
print(f" [2] {t('Graphical (server + GNOME desktop)')}")
return input(t("Choice (number, blank = server): ")).strip() == "2"
def _qemu_host_timezone(self):
"""Fuseau de l'hôte. Défini une seule fois, dans deploy_qemu.py, qui
est aussi ce qui l'écrit dans le cloud-config : l'invite ne peut donc
@ -5557,6 +5708,7 @@ class TODO:
timezone = self._qemu_ask_timezone()
locale = self._qemu_ask_locale()
desktop = self._qemu_ask_desktop()
# 4) Option : installer ERPLibre dans ~/git/erplibre de chaque VM.
install = None
@ -5633,6 +5785,7 @@ class TODO:
"ssh_key": ssh_key,
"timezone": timezone,
"locale": locale,
"desktop": desktop,
"install": install,
"add_ssh_config": add_ssh_config,
"parallelism": parallelism,
@ -5700,6 +5853,7 @@ class TODO:
deployed = list(spec.get("existing") or [])
install = spec.get("install")
install_branch = install["branch"] if install else None
desktop = bool(spec.get("desktop"))
ssh_key = spec.get("ssh_key")
add_ssh_config = spec["add_ssh_config"]
parallelism = spec["parallelism"]
@ -5741,7 +5895,9 @@ class TODO:
# install) : une boucle EN SÉRIE bloquait plusieurs minutes par VM
# émulée SANS sortie -> le dashboard « n'ouvrait jamais ».
ip_map = {}
if deployed and (add_ssh_config or install_branch):
# `desktop` compte aussi : sans IP résolue, l'installation du bureau
# n'aurait aucune VM à joindre.
if deployed and (add_ssh_config or install_branch or desktop):
labels = {
nm: f"{k}/{len(deployed)}" for k, nm in enumerate(deployed, 1)
}
@ -5758,16 +5914,20 @@ class TODO:
name, "erplibre", ip, identity_file=identity
)
# 7) Installation ERPLibre (clone + make) si demandée.
if install:
if install["monitor"]:
# 7) Installation ERPLibre (clone + make) et/ou bureau GNOME. Le bureau
# ne dépend PAS d'ERPLibre : une VM peut être voulue graphique et nue.
# Il passe par la même commande distante, donc par le même suivi.
if install or desktop:
monitor = install["monitor"] if install else True
if monitor:
# Installs détachées en parallèle + dashboard Textual.
self._qemu_install_erplibre_monitored(
deployed,
install_branch,
ip_map,
install["cmd"],
install["prod"],
install["cmd"] if install else None,
install["prod"] if install else False,
desktop=desktop,
)
else:
print(
@ -5782,6 +5942,7 @@ class TODO:
ip_map.get(name),
install["cmd"],
install["prod"],
desktop=desktop,
)
# Sommaire TOTAL (déploiement + résolution IP + ssh_config + install

View file

@ -2023,6 +2023,30 @@ TRANSLATIONS = {
"fr": "Choix (numéro, vide = rouvrir) : ",
"en": "Choice (number, blank = reopen): ",
},
"VM type:": {
"fr": "Type de VM :",
"en": "VM type:",
},
"Server (no graphical interface)": {
"fr": "Serveur (sans interface graphique)",
"en": "Server (no graphical interface)",
},
"Graphical (server + GNOME desktop)": {
"fr": "Graphique (serveur + bureau GNOME)",
"en": "Graphical (server + GNOME desktop)",
},
"Choice (number, blank = server): ": {
"fr": "Choix (numero, vide = serveur) : ",
"en": "Choice (number, blank = server): ",
},
"Installing the GNOME desktop (long)": {
"fr": "Installation du bureau GNOME (long)",
"en": "Installing the GNOME desktop (long)",
},
"Remote desktop:": {
"fr": "Bureau distant :",
"en": "Remote desktop:",
},
"One run per install": {
"fr": "une execution par installation",
"en": "One run per install",