Integrate claude, improve selenium and support qemu #1

Open
mathben wants to merge 224 commits from develop into master
3 changed files with 98 additions and 17 deletions
Showing only changes of commit 8278c38f46 - Show all commits

View file

@ -978,8 +978,23 @@ def build_cloud_config(
" qemu-guest-agent) || (command -v dnf >/dev/null && dnf install -y" " qemu-guest-agent) || (command -v dnf >/dev/null && dnf install -y"
" qemu-guest-agent) || (command -v pacman >/dev/null && pacman -S" " qemu-guest-agent) || (command -v pacman >/dev/null && pacman -S"
" --noconfirm qemu-guest-agent) || true", " --noconfirm qemu-guest-agent) || true",
" - systemctl enable --now qemu-guest-agent 2>/dev/null" # Autorise guest-exec (bloqué par défaut sur certaines distros).
" || systemctl enable --now qemu-ga 2>/dev/null || true", # On VIDE la liste de blocage (block-rpcs/blacklist) plutôt que
# d'utiliser allow-rpcs (liste BLANCHE : casserait les autres RPC).
# Les deux clés couvrent les versions anciennes (blacklist) et
# récentes (block-rpcs) de qemu-ga.
" - mkdir -p /etc/qemu && printf '[general]\\nblock-rpcs=\\n"
"blacklist=\\n' > /etc/qemu/qemu-ga.conf || true",
# Fedora/RHEL bloquent guest-exec via /etc/sysconfig/qemu-ga.
" - [ -f /etc/sysconfig/qemu-ga ] && sed -i"
" 's/^BLACKLIST_RPC=.*/BLACKLIST_RPC=/'"
" /etc/sysconfig/qemu-ga || true",
" - systemctl enable qemu-guest-agent 2>/dev/null"
" || systemctl enable qemu-ga 2>/dev/null || true",
# restart (et non enable --now) : recharge la config si l'agent était
# déjà démarré par l'image cloud.
" - systemctl restart qemu-guest-agent 2>/dev/null"
" || systemctl restart qemu-ga 2>/dev/null || true",
] ]
return "\n".join(lines) + "\n" return "\n".join(lines) + "\n"

View file

@ -470,7 +470,7 @@ def run_monitor(manifest_path: str, run_app: bool = True):
class Monitor(App): class Monitor(App):
CSS = """ CSS = """
DataTable { width: 58; border: solid $accent; } DataTable { width: 74; height: 1fr; overflow-x: auto; border: solid $accent; }
RichLog { border: solid $accent; } RichLog { border: solid $accent; }
#telemetry { height: 1; color: $text-muted; } #telemetry { height: 1; color: $text-muted; }
#stats { height: 1; color: $accent; } #stats { height: 1; color: $accent; }
@ -526,11 +526,14 @@ def run_monitor(manifest_path: str, run_app: bool = True):
table = DataTable(id="vms", cursor_type="row") table = DataTable(id="vms", cursor_type="row")
# Clés de colonnes explicites : update_cell() les référence. # Clés de colonnes explicites : update_cell() les référence.
# La colonne « ⚠ » (erreurs détectées) est à GAUCHE d'« État ». # La colonne « ⚠ » (erreurs détectées) est à GAUCHE d'« État ».
table.add_column("VM", key="vm") # Largeurs FIXES pour les colonnes courtes -> l'État n'est plus
table.add_column("⚠", key="err") # tronqué (« ❌ effacée », « ⏸ en pause » lisibles) ; la table
table.add_column("État", key="state") # défile horizontalement (overflow-x) pour les noms de VM longs.
table.add_column("Durée", key="elapsed") table.add_column("VM", key="vm", width=26)
table.add_column("Disque", key="disk") table.add_column("⚠", key="err", width=4)
table.add_column("État", key="state", width=12)
table.add_column("Durée", key="elapsed", width=7)
table.add_column("Disque", key="disk", width=8)
for vm in vms: for vm in vms:
table.add_row( table.add_row(
vm["name"], "", "⏳", "--:--", "-", key=vm["name"] vm["name"], "", "⏳", "--:--", "-", key=vm["name"]
@ -835,15 +838,8 @@ def run_monitor(manifest_path: str, run_app: bool = True):
if not vm or not vm.get("ip"): if not vm or not vm.get("ip"):
self.notify("Pas d'IP pour cette VM.", title="Web") self.notify("Pas d'IP pour cette VM.", title="Web")
return return
browser = cli_browser() browser = self._choose_browser()
if not browser: if not browser:
browser = self._install_cli_browser()
if not browser:
self.notify(
"Aucun navigateur CLI disponible.",
title="Web",
severity="warning",
)
return return
url = f"http://{vm['ip']}:8069" url = f"http://{vm['ip']}:8069"
with self.suspend(): with self.suspend():
@ -863,6 +859,40 @@ def run_monitor(manifest_path: str, run_app: bool = True):
except EOFError: except EOFError:
pass pass
def _choose_browser(self):
"""Offre la LISTE des navigateurs CLI installés et laisse choisir
lequel utiliser pour voir la page. Si aucun n'est installé, propose
d'en installer un. Renvoie le binaire choisi, ou None."""
available = [b for b in CLI_BROWSERS if shutil.which(b)]
if not available:
browser = self._install_cli_browser()
if not browser:
self.notify(
"Aucun navigateur CLI disponible.",
title="Web",
severity="warning",
)
return browser
with self.suspend():
print("Quel navigateur utiliser pour voir la page ?")
for i, b in enumerate(available, 1):
print(f" [{i}] {b}{' *' if i == 1 else ''}")
print(" [i] Installer un autre navigateur")
sel = input(
f"Choix (numéro, vide = {available[0]}) : "
).strip().lower()
if sel == "i":
return self._install_cli_browser()
if not sel:
return available[0]
try:
idx = int(sel) - 1
if 0 <= idx < len(available):
return available[idx]
except ValueError:
pass
return available[0]
def _install_cli_browser(self): def _install_cli_browser(self):
"""Demande QUEL navigateur CLI installer (w3m/lynx/links/elinks), """Demande QUEL navigateur CLI installer (w3m/lynx/links/elinks),
affiche la commande, l'exécute après validation. Renvoie le binaire affiche la commande, l'exécute après validation. Renvoie le binaire

View file

@ -2533,12 +2533,48 @@ class TODO:
pass pass
return profiles[0] # défaut : ERPLibre + Odoo 18 return profiles[0] # défaut : ERPLibre + Odoo 18
@staticmethod
def _qemu_odoo_service_cmd():
"""Snippet shell (exécuté dans la VM) qui installe ERPLibre/Odoo comme
service systemd (inspiré de script/systemd/install_daemon.sh) puis
l'active + démarre. N'est ajouté QUE pour les profils avec Odoo."""
return (
'SVC_USER=$(whoami); SVC_GROUP=$(id -gn); '
'SVC_DIR="$HOME/git/erplibre"; '
"sudo tee /etc/systemd/system/erplibre.service >/dev/null <<UNIT\n"
"[Unit]\n"
"Description=ERPLibre\n"
"Requires=postgresql.service\n"
"After=network.target network-online.target postgresql.service\n"
"\n"
"[Service]\n"
"Type=simple\n"
"User=$SVC_USER\n"
"Group=$SVC_GROUP\n"
"Restart=always\n"
"RestartSec=5\n"
"ExecStart=$SVC_DIR/run.sh\n"
"WorkingDirectory=$SVC_DIR\n"
"StandardOutput=journal+console\n"
"\n"
"[Install]\n"
"WantedBy=multi-user.target\n"
"UNIT\n"
"sudo systemctl daemon-reload; "
"sudo systemctl enable --now erplibre.service"
)
def _qemu_erplibre_remote_cmd(self, branch, final_cmd=None): def _qemu_erplibre_remote_cmd(self, branch, final_cmd=None):
"""Script d'installation ERPLibre exécuté DANS la VM (curl/git/make """Script d'installation ERPLibre exécuté DANS la VM (curl/git/make
puis clone + `final_cmd` dans ~/git/erplibre). `final_cmd` par défaut : puis clone + `final_cmd` dans ~/git/erplibre). `final_cmd` par défaut :
install_os + install_odoo_18.""" install_os + install_odoo_18."""
if not final_cmd: if not final_cmd:
final_cmd = f"make install_os && make {self.ERPLIBRE_ODOO_TARGET}" final_cmd = f"make install_os && make {self.ERPLIBRE_ODOO_TARGET}"
# Profils AVEC Odoo (install_odoo*) uniquement : après l'install, on
# enregistre Odoo comme service systemd (enable + start). Pas pour
# « ERPLibre seul », « mobile » ni « Déploiement ».
if "install_odoo" in final_cmd:
final_cmd = f"{final_cmd} && {self._qemu_odoo_service_cmd()}"
return ( return (
"set -e; " "set -e; "
# Attendre la FIN de cloud-init : pendant sa phase « paquets » il # Attendre la FIN de cloud-init : pendant sa phase « paquets » il