[ADD] qemu: choose the application store of a graphical VM
snap was not a choice but a fate: we disabled snapd, then gnome-core
pulled a Firefox snap that froze the install for thirty minutes. The
previous fix imposed "deb". Three answers are now offered.
deb nothing but .deb, epiphany-browser as the browser. Default,
and lightest: nothing extra to download.
flatpak Flatpak tooling as well, WITHOUT the Flathub remote or any
install. The machine is ready, the admin picks its remotes.
snap Ubuntu's default, snapd left running and Firefox as a snap.
It is the only mode where snapd is not disabled -- disabling
it was precisely the cause of the freeze.
The question is only asked when it means something: at least one
graphical VM on a distro shipping snapd, Ubuntu alone here. A server
pulls no snap, and Debian ships none. The TUI greys the choice out and
says why; the CLI does not ask.
Verified on a 26.04 VM: snapd is indeed preinstalled (2.75.2), flatpak
and its GNOME Software plugin are packaged, and firefox-esr does not
exist on Ubuntu.
--- FR ---
snap n'était plus un choix mais une fatalité : on coupait snapd, puis
gnome-core tirait un Firefox-snap qui figeait l'installation trente
minutes. Le correctif précédent imposait « deb ». Trois réponses sont
maintenant offertes.
deb rien que des .deb, epiphany-browser comme navigateur. Défaut,
et le plus léger : rien de plus à télécharger.
flatpak l'outillage Flatpak en plus, SANS dépôt Flathub ni
installation. La machine est prête, l'administrateur choisit
ses dépôts.
snap le défaut d'Ubuntu, snapd laissé actif et Firefox en snap.
C'est le seul mode où snapd n'est pas coupé — l'y couper
était précisément la cause du blocage.
La question n'est posée que lorsqu'elle a un sens : au moins une VM
graphique sur une distribution qui livre snapd, Ubuntu seule ici. Un
serveur ne tire aucun snap, et Debian n'en livre pas. La TUI grise le
choix et dit pourquoi ; la CLI ne le demande pas.
Vérifié sur une VM 26.04 : snapd y est bien préinstallé (2.75.2),
flatpak et son greffon GNOME Software sont empaquetés, et firefox-esr
n'existe pas sur Ubuntu.
Assisted-by: Claude Opus 5
This commit is contained in:
parent
f32fa6c111
commit
1726cb5dab
2 changed files with 112 additions and 15 deletions
|
|
@ -212,6 +212,7 @@ def build_spec(vms, domains, form):
|
|||
"timezone": form.get("timezone", ""),
|
||||
"desktop": form.get("desktop", ""),
|
||||
"python_provider": form.get("python_provider", ""),
|
||||
"app_store": form.get("app_store", "deb"),
|
||||
"install": form["install"],
|
||||
"add_ssh_config": form["add_ssh_config"],
|
||||
"parallelism": form["parallelism"],
|
||||
|
|
@ -278,6 +279,10 @@ def run_deploy_form(ctx, run_app: bool = True):
|
|||
desktops = list(ctx.get("desktops") or [])
|
||||
# Architectures pour lesquelles mise publie un binaire.
|
||||
mise_arches = set(ctx.get("mise_arches") or ())
|
||||
# [(clé, libellé)] des magasins d'applications, et les distributions qui
|
||||
# livrent snapd — la question n'a de sens que pour celles-là, graphiques.
|
||||
app_stores = list(ctx.get("app_stores") or [])
|
||||
snap_distros = set(ctx.get("snap_distros") or ())
|
||||
defaults = ctx.get("defaults") or {}
|
||||
result = {"spec": None}
|
||||
|
||||
|
|
@ -500,6 +505,14 @@ def run_deploy_form(ctx, run_app: bool = True):
|
|||
f"{t('Graphical (server + desktop):')} {label}",
|
||||
value=defaults.get("desktop", "") == key,
|
||||
)
|
||||
if app_stores:
|
||||
yield Static(
|
||||
t("Application store:"), classes="grouptitle"
|
||||
)
|
||||
with RadioSet(id="f_store"):
|
||||
for i, (_k, label) in enumerate(app_stores):
|
||||
yield RadioButton(label, value=i == 0)
|
||||
yield Static("", id="storewarn")
|
||||
yield Static("ERPLibre", classes="grouptitle")
|
||||
yield Checkbox(
|
||||
t("Install ERPLibre"),
|
||||
|
|
@ -655,6 +668,7 @@ def run_deploy_form(ctx, run_app: bool = True):
|
|||
row["custom"] = bool(self.overrides.get(entry_key(entry)))
|
||||
self._render_plan()
|
||||
self._render_mise()
|
||||
self._render_store()
|
||||
|
||||
def _render_mise(self):
|
||||
"""Grise le choix quand aucune VM retenue n'est servie par mise,
|
||||
|
|
@ -690,6 +704,36 @@ def run_deploy_form(ctx, run_app: bool = True):
|
|||
index = self.query_one("#f_python", RadioSet).pressed_index
|
||||
return "pyenv" if index == 1 else "mise"
|
||||
|
||||
def _app_store(self):
|
||||
"""Magasin retenu. Sans VM concernée, la réponse est « deb » :
|
||||
elle ne change rien, et laisser passer « snap » réactiverait snapd
|
||||
pour rien."""
|
||||
if not app_stores or not self._app_store_needed():
|
||||
return "deb"
|
||||
index = self.query_one("#f_store", RadioSet).pressed_index
|
||||
if index is None or not (0 <= index < len(app_stores)):
|
||||
return app_stores[0][0]
|
||||
return app_stores[index][0]
|
||||
|
||||
def _app_store_needed(self):
|
||||
"""Au moins une VM graphique sur une distribution qui livre snapd."""
|
||||
return any(
|
||||
vm.get("desktop") and vm["distro"] in snap_distros
|
||||
for vm in self.vms
|
||||
)
|
||||
|
||||
def _render_store(self):
|
||||
"""Grise le choix quand aucune VM ne le concerne, et dit pourquoi."""
|
||||
if not app_stores:
|
||||
return
|
||||
needed = self._app_store_needed()
|
||||
self.query_one("#f_store", RadioSet).disabled = not needed
|
||||
self.query_one("#storewarn", Static).update(
|
||||
""
|
||||
if needed
|
||||
else f" {t('No graphical VM on a snap-based distro.')}"
|
||||
)
|
||||
|
||||
def _mise_usable(self):
|
||||
return any(vm["arch"] in mise_arches for vm in self.vms)
|
||||
|
||||
|
|
@ -1157,6 +1201,7 @@ def run_deploy_form(ctx, run_app: bool = True):
|
|||
or "",
|
||||
"desktop": self._default_desktop(),
|
||||
"python_provider": self._python_provider(),
|
||||
"app_store": self._app_store(),
|
||||
"install": install,
|
||||
"add_ssh_config": self.query_one("#f_sshcfg", Checkbox).value,
|
||||
# Une exécution par installation : le nombre de VM retenues
|
||||
|
|
|
|||
|
|
@ -4457,7 +4457,7 @@ class TODO:
|
|||
)
|
||||
|
||||
@staticmethod
|
||||
def _qemu_no_auto_upgrade(prod):
|
||||
def _qemu_no_auto_upgrade(prod, app_store="deb"):
|
||||
"""Coupe les mises à jour automatiques sur une VM de DÉVELOPPEMENT.
|
||||
|
||||
Vécu sur erplibre-ubuntu-2404 : unattended-upgrades s'est déclenché en
|
||||
|
|
@ -4485,13 +4485,20 @@ class TODO:
|
|||
"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; "
|
||||
# multi-user.target attend snapd.seeded. C'est du temps payé pour
|
||||
# rien quand aucun snap n'est voulu. On désactive plutôt que
|
||||
# désinstaller, pour rester réversible d'un « systemctl enable ».
|
||||
#
|
||||
# Sauf si le magasin RETENU est snap : le couper puis laisser un
|
||||
# postinst appeler « snap install » est exactement ce qui figeait
|
||||
# une VM graphique trente minutes durant.
|
||||
+ (
|
||||
""
|
||||
if app_store == "snap"
|
||||
else "sudo systemctl disable --now snapd.seeded.service "
|
||||
"snapd.service snapd.socket snapd.apparmor.service "
|
||||
">/dev/null 2>&1 || true; "
|
||||
)
|
||||
)
|
||||
|
||||
# Miroirs openSUSE préférés, du plus proche au dernier recours. Le
|
||||
|
|
@ -4602,7 +4609,7 @@ class TODO:
|
|||
f'echo " {t("openSUSE mirror:")} $zm"; break; fi; done; '
|
||||
)
|
||||
|
||||
def _qemu_desktop_remote_cmd(self, flavour="gnome"):
|
||||
def _qemu_desktop_remote_cmd(self, flavour="gnome", app_store="deb"):
|
||||
"""Bloc shell installant le bureau choisi + 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
|
||||
|
|
@ -4619,7 +4626,7 @@ class TODO:
|
|||
"sudo DEBIAN_FRONTEND=noninteractive "
|
||||
"apt-get -o DPkg::Lock::Timeout=600 install -y "
|
||||
f"{de['apt']} {rem['apt']['packages']} "
|
||||
f"{self._QEMU_APT_NO_SNAP}; "
|
||||
f"{self._qemu_apt_store_pkgs(app_store)}; "
|
||||
"elif command -v dnf >/dev/null 2>&1; then "
|
||||
# Cascade d'environnements : le premier qui existe gagne. Un
|
||||
# environnement absent fait rendre 1 à dnf sans rien installer,
|
||||
|
|
@ -4704,6 +4711,7 @@ class TODO:
|
|||
prod=False,
|
||||
desktop=False,
|
||||
python_provider="",
|
||||
app_store="deb",
|
||||
):
|
||||
"""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
|
||||
|
|
@ -4723,8 +4731,8 @@ class TODO:
|
|||
return (
|
||||
"set -e; "
|
||||
+ self._qemu_cloud_init_wait()
|
||||
+ self._qemu_no_auto_upgrade(prod)
|
||||
+ self._qemu_desktop_remote_cmd(desktop)
|
||||
+ self._qemu_no_auto_upgrade(prod, app_store)
|
||||
+ self._qemu_desktop_remote_cmd(desktop, app_store)
|
||||
)
|
||||
if not final_cmd:
|
||||
final_cmd = f"make install_os && make {self.ERPLIBRE_ODOO_TARGET}"
|
||||
|
|
@ -4749,7 +4757,7 @@ 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 = self._qemu_no_auto_upgrade(prod)
|
||||
no_auto_upgrade = self._qemu_no_auto_upgrade(prod, app_store)
|
||||
return (
|
||||
"set -e; " + self._qemu_cloud_init_wait()
|
||||
# Coupé AVANT les apt-get ci-dessous : sinon apt-daily peut reprendre
|
||||
|
|
@ -4758,7 +4766,12 @@ class TODO:
|
|||
# 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(desktop) if desktop else "") +
|
||||
+ (
|
||||
self._qemu_desktop_remote_cmd(desktop, app_store)
|
||||
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
|
||||
|
|
@ -4859,6 +4872,7 @@ class TODO:
|
|||
prod=False,
|
||||
desktop=False,
|
||||
python_provider="",
|
||||
app_store="deb",
|
||||
):
|
||||
"""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.
|
||||
|
|
@ -4881,6 +4895,7 @@ class TODO:
|
|||
prod,
|
||||
"" if desk_map else desktop,
|
||||
python_provider,
|
||||
app_store,
|
||||
)
|
||||
try:
|
||||
mod = self._qemu_import_module()
|
||||
|
|
@ -4911,6 +4926,7 @@ class TODO:
|
|||
prod,
|
||||
desk_map.get(name, ""),
|
||||
python_provider,
|
||||
app_store,
|
||||
)
|
||||
vms.append(entry)
|
||||
else:
|
||||
|
|
@ -4953,6 +4969,7 @@ class TODO:
|
|||
prod=False,
|
||||
desktop=False,
|
||||
python_provider="",
|
||||
app_store="deb",
|
||||
):
|
||||
"""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 ;
|
||||
|
|
@ -4974,7 +4991,7 @@ class TODO:
|
|||
)
|
||||
return
|
||||
remote = self._qemu_erplibre_remote_cmd(
|
||||
branch, final_cmd, prod, desktop, python_provider
|
||||
branch, final_cmd, prod, desktop, python_provider, app_store
|
||||
)
|
||||
ssh_opts = (
|
||||
"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "
|
||||
|
|
@ -5689,6 +5706,8 @@ class TODO:
|
|||
"extra_disk_gb": self.ERPLIBRE_EXTRA_DISK_GB,
|
||||
"desktop_disk_gb": self.QEMU_DESKTOP_EXTRA_DISK_GB,
|
||||
"mise_arches": self.QEMU_MISE_ARCHES,
|
||||
"app_stores": [(k, t(lbl)) for k, lbl in self.QEMU_APP_STORES],
|
||||
"snap_distros": self.QEMU_SNAP_DISTROS,
|
||||
"desktops": [
|
||||
(k, v["label"]) for k, v in self._QEMU_DESKTOP.items()
|
||||
],
|
||||
|
|
@ -5979,6 +5998,30 @@ class TODO:
|
|||
return ""
|
||||
return flavours[index] if 0 <= index < len(flavours) else ""
|
||||
|
||||
@classmethod
|
||||
def _qemu_app_store_needed(cls, vms):
|
||||
"""Vrai si au moins une VM du parc est à la fois graphique et d'une
|
||||
distribution qui livre snapd. Ailleurs la question n'a pas d'objet :
|
||||
un serveur ne tire aucun snap, et Debian ou Fedora n'en livrent pas."""
|
||||
return any(
|
||||
vm.get("desktop") and vm.get("distro") in cls.QEMU_SNAP_DISTROS
|
||||
for vm in vms
|
||||
)
|
||||
|
||||
def _qemu_ask_app_store(self, vms):
|
||||
"""Magasin d'applications des VM graphiques Ubuntu."""
|
||||
if not self._qemu_app_store_needed(vms):
|
||||
return "deb"
|
||||
print(f"\n{t('Application store (graphical Ubuntu VMs):')}")
|
||||
for i, (_key, label) in enumerate(self.QEMU_APP_STORES, 1):
|
||||
star = " *" if i == 1 else ""
|
||||
print(f" [{i}] {t(label)}{star}")
|
||||
print(f" ⚠ {t('snap needs the store; slow under emulation.')}")
|
||||
answer = input(f"{t('Choice')} [1]: ").strip() or "1"
|
||||
if answer.isdigit() and 1 <= int(answer) <= len(self.QEMU_APP_STORES):
|
||||
return self.QEMU_APP_STORES[int(answer) - 1][0]
|
||||
return "deb"
|
||||
|
||||
def _qemu_ask_python_provider(self, arches):
|
||||
"""mise (CPython précompilé) ou pyenv (compilation).
|
||||
|
||||
|
|
@ -6064,6 +6107,11 @@ class TODO:
|
|||
timezone = self._qemu_ask_timezone()
|
||||
locale = self._qemu_ask_locale()
|
||||
desktop = self._qemu_ask_desktop()
|
||||
# La CLI ne pose qu'un type pour tout le parc : on le recopie sur chaque
|
||||
# VM avant de décider du magasin, qui ne concerne que les graphiques.
|
||||
for _vm in vms:
|
||||
_vm.setdefault("desktop", desktop)
|
||||
app_store = self._qemu_ask_app_store(vms)
|
||||
python_provider = self._qemu_ask_python_provider(
|
||||
[vm["arch"] for vm in vms]
|
||||
)
|
||||
|
|
@ -6145,6 +6193,7 @@ class TODO:
|
|||
"locale": locale,
|
||||
"desktop": desktop,
|
||||
"python_provider": python_provider,
|
||||
"app_store": app_store,
|
||||
"install": install,
|
||||
"add_ssh_config": add_ssh_config,
|
||||
"parallelism": parallelism,
|
||||
|
|
@ -6225,6 +6274,7 @@ class TODO:
|
|||
desktop_map.setdefault(_name, desktop_default)
|
||||
desktop = next((d for d in desktop_map.values() if d), "")
|
||||
python_provider = spec.get("python_provider") or ""
|
||||
app_store = spec.get("app_store") or "deb"
|
||||
ssh_key = spec.get("ssh_key")
|
||||
add_ssh_config = spec["add_ssh_config"]
|
||||
parallelism = spec["parallelism"]
|
||||
|
|
@ -6300,6 +6350,7 @@ class TODO:
|
|||
install["prod"] if install else False,
|
||||
desktop=desktop_map,
|
||||
python_provider=python_provider,
|
||||
app_store=app_store,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
|
|
@ -6316,6 +6367,7 @@ class TODO:
|
|||
install["prod"],
|
||||
desktop=desktop_map.get(name, ""),
|
||||
python_provider=python_provider,
|
||||
app_store=app_store,
|
||||
)
|
||||
|
||||
# Sommaire TOTAL (déploiement + résolution IP + ssh_config + install
|
||||
|
|
|
|||
Loading…
Reference in a new issue