Compare commits
5 commits
808e37cd59
...
6506e5cf9f
| Author | SHA1 | Date | |
|---|---|---|---|
| 6506e5cf9f | |||
| 19c5f41751 | |||
| 233fdf2d2b | |||
| e8c60a2d46 | |||
| bcb77df2d3 |
2 changed files with 41 additions and 26 deletions
|
|
@ -685,16 +685,21 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
# Largeurs FIXES pour les colonnes courtes -> l'État n'est plus
|
# Largeurs FIXES pour les colonnes courtes -> l'État n'est plus
|
||||||
# tronqué (« ❌ effacée », « ⏸ en pause » lisibles) ; la table
|
# tronqué (« ❌ effacée », « ⏸ en pause » lisibles) ; la table
|
||||||
# défile horizontalement (overflow-x) pour les noms de VM longs.
|
# défile horizontalement (overflow-x) pour les noms de VM longs.
|
||||||
table.add_column("VM", key="vm", width=26)
|
# « # » : numéro de séquence de la VM (première colonne).
|
||||||
|
table.add_column("#", key="seq", width=3)
|
||||||
|
table.add_column("VM", key="vm", width=22)
|
||||||
table.add_column("⚠", key="err", width=4)
|
table.add_column("⚠", key="err", width=4)
|
||||||
table.add_column("État", key="state", width=12)
|
# width=8 : le plus long libellé restant est « ⏸ pause » (7) —
|
||||||
|
# « effacée » (10) est remplacé par l'icône 🗑 (voir plus bas).
|
||||||
|
table.add_column("État", key="state", width=8)
|
||||||
# « Odoo » : l'UI web répond-elle sur :8069 ? (🟢 up / — down)
|
# « Odoo » : l'UI web répond-elle sur :8069 ? (🟢 up / — down)
|
||||||
table.add_column("Odoo", key="odoo", width=6)
|
table.add_column("Odoo", key="odoo", width=6)
|
||||||
table.add_column("Durée", key="elapsed", width=7)
|
table.add_column("Durée", key="elapsed", width=7)
|
||||||
table.add_column("Disque", key="disk", width=8)
|
table.add_column("Disque", key="disk", width=8)
|
||||||
for vm in vms:
|
for i, vm in enumerate(vms, 1):
|
||||||
table.add_row(
|
table.add_row(
|
||||||
vm["name"], "", "⏳", "—", "--:--", "-", key=vm["name"]
|
str(i), vm["name"], "", "⏳", "—", "--:--", "-",
|
||||||
|
key=vm["name"],
|
||||||
)
|
)
|
||||||
# max_lines borne la mémoire/rendu (un install verbeux × 30 VM).
|
# max_lines borne la mémoire/rendu (un install verbeux × 30 VM).
|
||||||
self._log = RichLog(
|
self._log = RichLog(
|
||||||
|
|
@ -839,9 +844,9 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
ds = self._domstate.get(name)
|
ds = self._domstate.get(name)
|
||||||
if ds == "gone":
|
if ds == "gone":
|
||||||
self._final[name] = ("deleted", None, now - started)
|
self._final[name] = ("deleted", None, now - started)
|
||||||
self._set_cell(
|
# Icône seule (VM effacée) : évite « ❌ effacée » (10
|
||||||
table, name, "state", f"❌ {t('deleted')}"
|
# cellules) qui forçait une colonne État large.
|
||||||
)
|
self._set_cell(table, name, "state", "🗑")
|
||||||
continue
|
continue
|
||||||
st = status.get(name)
|
st = status.get(name)
|
||||||
if st is None:
|
if st is None:
|
||||||
|
|
@ -882,9 +887,14 @@ def run_monitor(manifest_path: str, run_app: bool = True):
|
||||||
eta = (
|
eta = (
|
||||||
f" · ETA ~{_fmt_secs(max(remaining))}" if remaining else ""
|
f" · ETA ~{_fmt_secs(max(remaining))}" if remaining else ""
|
||||||
)
|
)
|
||||||
|
# Max de durée : la VM TERMINÉE la plus lente (pire cas).
|
||||||
|
max_dur = max(
|
||||||
|
(el for _s, _c, el in self._final.values()), default=0
|
||||||
|
)
|
||||||
|
maxd = f" · max {self._fmt(max_dur)}" if max_dur else ""
|
||||||
self.sub_title = (
|
self.sub_title = (
|
||||||
f"{done}/{len(vms)} {t('completed')} · "
|
f"{done}/{len(vms)} {t('completed')} · "
|
||||||
f"{self._fmt(now - started)}{eta}"
|
f"{self._fmt(now - started)}{eta}{maxd}"
|
||||||
)
|
)
|
||||||
if tele:
|
if tele:
|
||||||
self.query_one("#telemetry", Static).update(tele)
|
self.query_one("#telemetry", Static).update(tele)
|
||||||
|
|
|
||||||
|
|
@ -3080,26 +3080,28 @@ class TODO:
|
||||||
"""Snippet shell (exécuté dans la VM) qui installe ERPLibre/Odoo comme
|
"""Snippet shell (exécuté dans la VM) qui installe ERPLibre/Odoo comme
|
||||||
service systemd puis l'active. N'est ajouté QUE pour les profils Odoo.
|
service systemd puis l'active. N'est ajouté QUE pour les profils Odoo.
|
||||||
|
|
||||||
DEV : ERPLibre sous ~/home ; si SELinux est actif (Fedora), on lève le
|
DEV : ERPLibre sous ~/home. Un service système ne peut PAS exécuter
|
||||||
confinement du service (unconfined) — un service système ne peut pas
|
du user_home_t sous SELinux, et « SELinuxContext=unconfined » ne suffit
|
||||||
exécuter du user_home_t. Acceptable pour une VM de dev.
|
pas (transition init_t -> unconfined_t refusée -> toujours 203/EXEC).
|
||||||
|
Sur une VM de dev jetable, on passe donc SELinux en PERMISSIF (relâché).
|
||||||
PROD : ERPLibre sous /opt/erplibre (hors user_home_t) -> le service
|
PROD : ERPLibre sous /opt/erplibre (hors user_home_t) -> le service
|
||||||
reste CONFINÉ par SELinux ; on restaure les contextes (restorecon)."""
|
reste CONFINÉ par SELinux ; on restaure les contextes (restorecon)."""
|
||||||
svc_dir = self._qemu_install_dir(prod)
|
svc_dir = self._qemu_install_dir(prod)
|
||||||
|
selinux_shell = 'SELINUX_LINE=""; ' # pas de SELinuxContext (inefficace)
|
||||||
if prod:
|
if prod:
|
||||||
pre = (
|
pre = (
|
||||||
"command -v restorecon >/dev/null 2>&1 && "
|
"command -v restorecon >/dev/null 2>&1 && "
|
||||||
"sudo restorecon -R /opt/erplibre >/dev/null 2>&1 || true; "
|
"sudo restorecon -R /opt/erplibre >/dev/null 2>&1 || true; "
|
||||||
)
|
)
|
||||||
selinux_shell = 'SELINUX_LINE=""; ' # confiné : pas d'unconfined
|
|
||||||
else:
|
else:
|
||||||
pre = ""
|
# DEV : SELinux permissif (persistant) si actif -> le service peut
|
||||||
selinux_shell = (
|
# exécuter run.sh/venv sous /home.
|
||||||
'SELINUX_LINE=""; '
|
pre = (
|
||||||
"if command -v getenforce >/dev/null 2>&1 && "
|
"if command -v getenforce >/dev/null 2>&1 && "
|
||||||
'[ "$(getenforce)" != "Disabled" ]; then '
|
'[ "$(getenforce)" = "Enforcing" ]; then '
|
||||||
'SELINUX_LINE="SELinuxContext=unconfined_u:unconfined_r:'
|
"sudo setenforce 0 || true; "
|
||||||
'unconfined_t:s0"; fi; '
|
"sudo sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' "
|
||||||
|
"/etc/selinux/config 2>/dev/null || true; fi; "
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
f'SVC_USER=$(whoami); SVC_GROUP=$(id -gn); SVC_DIR="{svc_dir}"; '
|
f'SVC_USER=$(whoami); SVC_GROUP=$(id -gn); SVC_DIR="{svc_dir}"; '
|
||||||
|
|
@ -3157,14 +3159,17 @@ class TODO:
|
||||||
# (Fedora) et pacman (Arch).
|
# (Fedora) et pacman (Arch).
|
||||||
"PKGS='curl git make'; "
|
"PKGS='curl git make'; "
|
||||||
"if command -v apt-get >/dev/null 2>&1; then "
|
"if command -v apt-get >/dev/null 2>&1; then "
|
||||||
# DPkg::Lock::Timeout=600 : au 1er boot, cloud-init (install de
|
# Au 1er boot, cloud-init (install qemu-guest-agent) et/ou
|
||||||
# qemu-guest-agent) et/ou apt-daily.service tiennent le verrou apt.
|
# apt-daily.service tiennent le verrou apt. IMPORTANT :
|
||||||
# Sans attente, « apt-get install » échouait aussitôt (« Could not
|
# « DPkg::Lock::Timeout » NE couvre PAS le verrou
|
||||||
# get lock … held by process »). On ATTEND le verrou jusqu'à 10 min.
|
# /var/lib/apt/lists/lock -> « apt-get update » échouait AUSSITÔT
|
||||||
# update best-effort (|| true) puis install OBLIGATOIRE : sans le
|
# (« Could not get lock … lists/lock ») -> lists vides -> « Unable
|
||||||
# « || true », un « apt-get update » en échec (réseau) sautait
|
# to locate package git ». On RÉESSAIE donc update jusqu'à ce que
|
||||||
# l'install sans erreur (liste &&) -> git/make absents ensuite.
|
# le verrou se libère (et les lists soient peuplées), borné à ~5 min.
|
||||||
"sudo apt-get -o DPkg::Lock::Timeout=600 update -qq || true; "
|
"n=0; until sudo apt-get -o DPkg::Lock::Timeout=120 update -qq; do "
|
||||||
|
"n=$((n+1)); [ $n -ge 30 ] && break; "
|
||||||
|
'echo "apt verrouille (tentative $n), attente 10s..."; sleep 10; '
|
||||||
|
"done; "
|
||||||
"sudo apt-get -o DPkg::Lock::Timeout=600 install -y $PKGS; "
|
"sudo apt-get -o DPkg::Lock::Timeout=600 install -y $PKGS; "
|
||||||
"elif command -v dnf >/dev/null 2>&1; then "
|
"elif command -v dnf >/dev/null 2>&1; then "
|
||||||
# makecache (dnf5 choisit les miroirs les plus rapides) puis
|
# makecache (dnf5 choisit les miroirs les plus rapides) puis
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue