Integrate claude, improve selenium and support qemu #1
4 changed files with 418 additions and 0 deletions
1
Makefile
1
Makefile
|
|
@ -47,6 +47,7 @@ endif
|
|||
-include ./conf/make.installation.Makefile
|
||||
-include ./conf/make.installation.poetry.Makefile
|
||||
-include ./conf/make.test.Makefile
|
||||
-include ./conf/make.ssh.Makefile
|
||||
-include ./conf/make.todo.Makefile
|
||||
|
||||
# Include private Makefile
|
||||
|
|
|
|||
130
conf/make.ssh.Makefile
Normal file
130
conf/make.ssh.Makefile
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#######
|
||||
# SSH #
|
||||
#######
|
||||
|
||||
# Connection variables (override on command line or via environment)
|
||||
SSH_HOST ?=
|
||||
SSH_USER ?= erplibre
|
||||
SSH_PORT ?= 22
|
||||
SSH_KEY ?=
|
||||
SSH_PATH ?= ~/erplibre_deploy_2
|
||||
|
||||
# Target to run remotely via ssh_make
|
||||
SSH_TARGET ?= run
|
||||
|
||||
# Build SSH/rsync options from variables
|
||||
_SSH_KEY_OPT = $(if $(SSH_KEY),-i $(SSH_KEY),)
|
||||
_SSH_CMD = ssh -p $(SSH_PORT) -o StrictHostKeyChecking=accept-new $(_SSH_KEY_OPT)
|
||||
_RSYNC_SSH = ssh -p $(SSH_PORT) -o StrictHostKeyChecking=accept-new $(_SSH_KEY_OPT)
|
||||
|
||||
define _require_host
|
||||
@test -n "$(SSH_HOST)" || \
|
||||
(echo "Error: SSH_HOST is required. Usage: make $@ SSH_HOST=hostname [SSH_USER=erplibre] [SSH_PORT=22] [SSH_KEY=~/.ssh/id_rsa]" && exit 1)
|
||||
endef
|
||||
|
||||
# Test SSH connectivity
|
||||
.PHONY: ssh_check
|
||||
ssh_check:
|
||||
$(call _require_host)
|
||||
$(_SSH_CMD) $(SSH_USER)@$(SSH_HOST) "echo 'SSH connection to $(SSH_HOST) OK && uname -a'"
|
||||
|
||||
# Sync project files to remote (excludes venvs, addons, odoo sources, git)
|
||||
.PHONY: ssh_push
|
||||
ssh_push:
|
||||
$(call _require_host)
|
||||
rsync -avz --delete \
|
||||
--exclude='.venv.*/' \
|
||||
--exclude='addons/' \
|
||||
--exclude='odoo12.0/' \
|
||||
--exclude='odoo13.0/' \
|
||||
--exclude='odoo14.0/' \
|
||||
--exclude='odoo15.0/' \
|
||||
--exclude='odoo16.0/' \
|
||||
--exclude='odoo17.0/' \
|
||||
--exclude='odoo18.0/' \
|
||||
--exclude='.git/' \
|
||||
--exclude='private/' \
|
||||
--exclude='*.pyc' \
|
||||
--exclude='__pycache__/' \
|
||||
-e "$(_RSYNC_SSH)" \
|
||||
./ $(SSH_USER)@$(SSH_HOST):$(SSH_PATH)/
|
||||
|
||||
# Install ERPLibre on remote server (Odoo 18 default)
|
||||
.PHONY: ssh_install
|
||||
ssh_install:
|
||||
$(call _require_host)
|
||||
$(_SSH_CMD) $(SSH_USER)@$(SSH_HOST) "cd $(SSH_PATH) && make install_odoo_18"
|
||||
|
||||
# Start Odoo on remote server
|
||||
.PHONY: ssh_run
|
||||
ssh_run:
|
||||
$(call _require_host)
|
||||
$(_SSH_CMD) $(SSH_USER)@$(SSH_HOST) "cd $(SSH_PATH) && make run"
|
||||
|
||||
# Stop Odoo on remote server (via systemd if available, else kill process)
|
||||
.PHONY: ssh_stop
|
||||
ssh_stop:
|
||||
$(call _require_host)
|
||||
$(_SSH_CMD) $(SSH_USER)@$(SSH_HOST) \
|
||||
"systemctl --user stop erplibre 2>/dev/null || \
|
||||
sudo systemctl stop erplibre 2>/dev/null || \
|
||||
cd $(SSH_PATH) && make process_kill_odoo"
|
||||
|
||||
# Restart Odoo on remote server
|
||||
.PHONY: ssh_restart
|
||||
ssh_restart:
|
||||
$(call _require_host)
|
||||
$(_SSH_CMD) $(SSH_USER)@$(SSH_HOST) \
|
||||
"systemctl --user restart erplibre 2>/dev/null || \
|
||||
sudo systemctl restart erplibre 2>/dev/null || \
|
||||
(cd $(SSH_PATH) && make process_kill_odoo && make run)"
|
||||
|
||||
# Show service status on remote server
|
||||
.PHONY: ssh_status
|
||||
ssh_status:
|
||||
$(call _require_host)
|
||||
$(_SSH_CMD) $(SSH_USER)@$(SSH_HOST) \
|
||||
"systemctl --user status erplibre 2>/dev/null || \
|
||||
sudo systemctl status erplibre 2>/dev/null || \
|
||||
ps aux | grep odoo | grep -v grep"
|
||||
|
||||
# Stream logs from remote server
|
||||
.PHONY: ssh_logs
|
||||
ssh_logs:
|
||||
$(call _require_host)
|
||||
$(_SSH_CMD) $(SSH_USER)@$(SSH_HOST) \
|
||||
"journalctl --user -u erplibre -f 2>/dev/null || \
|
||||
sudo journalctl -u erplibre -f"
|
||||
|
||||
# Execute any make target on remote server (e.g.: make ssh_make SSH_HOST=host SSH_TARGET=db_create_db_test)
|
||||
.PHONY: ssh_make
|
||||
ssh_make:
|
||||
$(call _require_host)
|
||||
@test -n "$(SSH_TARGET)" || \
|
||||
(echo "Error: SSH_TARGET is required. Usage: make ssh_make SSH_HOST=hostname SSH_TARGET=make_target" && exit 1)
|
||||
$(_SSH_CMD) $(SSH_USER)@$(SSH_HOST) "cd $(SSH_PATH) && make $(SSH_TARGET)"
|
||||
|
||||
# Install systemd service on remote server
|
||||
.PHONY: ssh_install_systemd
|
||||
ssh_install_systemd:
|
||||
$(call _require_host)
|
||||
$(_SSH_CMD) $(SSH_USER)@$(SSH_HOST) \
|
||||
"cd $(SSH_PATH) && python3 script/systemd/install_daemon.py \
|
||||
--user $(SSH_USER) \
|
||||
--home-erplibre $(SSH_PATH) \
|
||||
--port 8069"
|
||||
|
||||
# Configure nginx + SSL on remote server (requires SSH_DOMAIN)
|
||||
SSH_DOMAIN ?=
|
||||
SSH_ADMIN_EMAIL ?=
|
||||
.PHONY: ssh_install_nginx
|
||||
ssh_install_nginx:
|
||||
$(call _require_host)
|
||||
@test -n "$(SSH_DOMAIN)" || \
|
||||
(echo "Error: SSH_DOMAIN is required. Usage: make ssh_install_nginx SSH_HOST=hostname SSH_DOMAIN=example.com [SSH_ADMIN_EMAIL=admin@example.com]" && exit 1)
|
||||
$(_SSH_CMD) $(SSH_USER)@$(SSH_HOST) \
|
||||
"cd $(SSH_PATH) && sudo python3 script/nginx/deploy_nginx_and_certbot.py \
|
||||
--generate_nginx \
|
||||
--run_certbot \
|
||||
--domain $(SSH_DOMAIN) \
|
||||
$(if $(SSH_ADMIN_EMAIL),--admin_email $(SSH_ADMIN_EMAIL),)"
|
||||
|
|
@ -651,6 +651,17 @@ class TODO:
|
|||
choices = [
|
||||
{"prompt_description": t("Clone ERPLibre locally (git clone)")},
|
||||
{"prompt_description": t("Configure sshfs")},
|
||||
{"prompt_description": t("SSH - Check connection")},
|
||||
{"prompt_description": t("SSH - Sync files (rsync)")},
|
||||
{"prompt_description": t("SSH - Install ERPLibre")},
|
||||
{"prompt_description": t("SSH - Start Odoo")},
|
||||
{"prompt_description": t("SSH - Stop Odoo")},
|
||||
{"prompt_description": t("SSH - Restart Odoo")},
|
||||
{"prompt_description": t("SSH - Service status")},
|
||||
{"prompt_description": t("SSH - View logs")},
|
||||
{"prompt_description": t("SSH - Run make target")},
|
||||
{"prompt_description": t("SSH - Install systemd service")},
|
||||
{"prompt_description": t("SSH - Configure nginx + SSL")},
|
||||
]
|
||||
help_info = self.fill_help_info(choices)
|
||||
|
||||
|
|
@ -663,6 +674,28 @@ class TODO:
|
|||
self._deploy_clone_erplibre()
|
||||
elif status == "2":
|
||||
self._configure_sshfs()
|
||||
elif status == "3":
|
||||
self._deploy_ssh_check()
|
||||
elif status == "4":
|
||||
self._deploy_ssh_push()
|
||||
elif status == "5":
|
||||
self._deploy_ssh_install()
|
||||
elif status == "6":
|
||||
self._deploy_ssh_run()
|
||||
elif status == "7":
|
||||
self._deploy_ssh_stop()
|
||||
elif status == "8":
|
||||
self._deploy_ssh_restart()
|
||||
elif status == "9":
|
||||
self._deploy_ssh_status()
|
||||
elif status == "10":
|
||||
self._deploy_ssh_logs()
|
||||
elif status == "11":
|
||||
self._deploy_ssh_make()
|
||||
elif status == "12":
|
||||
self._deploy_ssh_install_systemd()
|
||||
elif status == "13":
|
||||
self._deploy_ssh_install_nginx()
|
||||
else:
|
||||
print(t("Command not found !"))
|
||||
|
||||
|
|
@ -793,6 +826,171 @@ class TODO:
|
|||
except Exception as e:
|
||||
print(f"{t('Error mounting sshfs: ')}{e}")
|
||||
|
||||
def _get_ssh_params(self):
|
||||
"""Prompt for SSH connection parameters. Returns dict or None on cancel."""
|
||||
host = click.prompt(t("Remote host (user@hostname or hostname): ")).strip()
|
||||
if not host:
|
||||
print(t("SSH host is required!"))
|
||||
return None
|
||||
user = (
|
||||
click.prompt(t("SSH user (default: erplibre): ")).strip()
|
||||
or "erplibre"
|
||||
)
|
||||
port = click.prompt(t("SSH port (default: 22): ")).strip() or "22"
|
||||
key = (
|
||||
click.prompt(
|
||||
t("SSH key path (default: ~/.ssh/id_rsa, empty for none): ")
|
||||
).strip()
|
||||
)
|
||||
path = (
|
||||
click.prompt(t("Remote path (default: ~/erplibre_deploy_2): ")).strip()
|
||||
or "~/erplibre_deploy_2"
|
||||
)
|
||||
return {
|
||||
"SSH_HOST": host,
|
||||
"SSH_USER": user,
|
||||
"SSH_PORT": port,
|
||||
"SSH_KEY": key,
|
||||
"SSH_PATH": path,
|
||||
}
|
||||
|
||||
def _build_ssh_make_cmd(self, target, params, extra=None):
|
||||
"""Build a make SSH command string from params dict."""
|
||||
parts = [f"make {target}"]
|
||||
for k, v in params.items():
|
||||
if v:
|
||||
parts.append(f'{k}="{v}"')
|
||||
if extra:
|
||||
for k, v in extra.items():
|
||||
if v:
|
||||
parts.append(f'{k}="{v}"')
|
||||
return " ".join(parts)
|
||||
|
||||
def _deploy_ssh_check(self):
|
||||
params = self._get_ssh_params()
|
||||
if not params:
|
||||
return
|
||||
cmd = self._build_ssh_make_cmd("ssh_check", params)
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(
|
||||
cmd, source_erplibre=False, single_source_erplibre=True
|
||||
)
|
||||
|
||||
def _deploy_ssh_push(self):
|
||||
params = self._get_ssh_params()
|
||||
if not params:
|
||||
return
|
||||
cmd = self._build_ssh_make_cmd("ssh_push", params)
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(
|
||||
cmd, source_erplibre=False, single_source_erplibre=True
|
||||
)
|
||||
|
||||
def _deploy_ssh_install(self):
|
||||
params = self._get_ssh_params()
|
||||
if not params:
|
||||
return
|
||||
cmd = self._build_ssh_make_cmd("ssh_install", params)
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(
|
||||
cmd, source_erplibre=False, single_source_erplibre=True
|
||||
)
|
||||
|
||||
def _deploy_ssh_run(self):
|
||||
params = self._get_ssh_params()
|
||||
if not params:
|
||||
return
|
||||
cmd = self._build_ssh_make_cmd("ssh_run", params)
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(
|
||||
cmd, source_erplibre=False, single_source_erplibre=True
|
||||
)
|
||||
|
||||
def _deploy_ssh_stop(self):
|
||||
params = self._get_ssh_params()
|
||||
if not params:
|
||||
return
|
||||
cmd = self._build_ssh_make_cmd("ssh_stop", params)
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(
|
||||
cmd, source_erplibre=False, single_source_erplibre=True
|
||||
)
|
||||
|
||||
def _deploy_ssh_restart(self):
|
||||
params = self._get_ssh_params()
|
||||
if not params:
|
||||
return
|
||||
cmd = self._build_ssh_make_cmd("ssh_restart", params)
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(
|
||||
cmd, source_erplibre=False, single_source_erplibre=True
|
||||
)
|
||||
|
||||
def _deploy_ssh_status(self):
|
||||
params = self._get_ssh_params()
|
||||
if not params:
|
||||
return
|
||||
cmd = self._build_ssh_make_cmd("ssh_status", params)
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(
|
||||
cmd, source_erplibre=False, single_source_erplibre=True
|
||||
)
|
||||
|
||||
def _deploy_ssh_logs(self):
|
||||
params = self._get_ssh_params()
|
||||
if not params:
|
||||
return
|
||||
cmd = self._build_ssh_make_cmd("ssh_logs", params)
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(
|
||||
cmd, source_erplibre=False, single_source_erplibre=True
|
||||
)
|
||||
|
||||
def _deploy_ssh_make(self):
|
||||
params = self._get_ssh_params()
|
||||
if not params:
|
||||
return
|
||||
target = click.prompt(t("Make target to run remotely: ")).strip()
|
||||
if not target:
|
||||
print(t("SSH host is required!"))
|
||||
return
|
||||
cmd = self._build_ssh_make_cmd(
|
||||
"ssh_make", params, extra={"SSH_TARGET": target}
|
||||
)
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(
|
||||
cmd, source_erplibre=False, single_source_erplibre=True
|
||||
)
|
||||
|
||||
def _deploy_ssh_install_systemd(self):
|
||||
params = self._get_ssh_params()
|
||||
if not params:
|
||||
return
|
||||
cmd = self._build_ssh_make_cmd("ssh_install_systemd", params)
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(
|
||||
cmd, source_erplibre=False, single_source_erplibre=True
|
||||
)
|
||||
|
||||
def _deploy_ssh_install_nginx(self):
|
||||
params = self._get_ssh_params()
|
||||
if not params:
|
||||
return
|
||||
domain = click.prompt(t("Domain name (e.g.: example.com): ")).strip()
|
||||
if not domain:
|
||||
print(t("SSH host is required!"))
|
||||
return
|
||||
email = click.prompt(t("Admin email for SSL certificate: ")).strip()
|
||||
cmd = self._build_ssh_make_cmd(
|
||||
"ssh_install_nginx",
|
||||
params,
|
||||
extra={"SSH_DOMAIN": domain, "SSH_ADMIN_EMAIL": email},
|
||||
)
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(
|
||||
cmd, source_erplibre=False, single_source_erplibre=True
|
||||
)
|
||||
|
||||
def prompt_execute_code(self):
|
||||
print(f"🤖 {t('What do you need for development?')}")
|
||||
# help_info = """Commande :
|
||||
|
|
|
|||
|
|
@ -722,6 +722,95 @@ TRANSLATIONS = {
|
|||
"fr": "Erreur lors de l'ajout de l'automatisation : ",
|
||||
"en": "Error adding automation: ",
|
||||
},
|
||||
# SSH Deploy section
|
||||
"Deploy - Deploy ERPLibre via SSH": {
|
||||
"fr": "Déploiement - Déployer ERPLibre via SSH",
|
||||
"en": "Deploy - Deploy ERPLibre via SSH",
|
||||
},
|
||||
"SSH deployment tools!": {
|
||||
"fr": "Outils de déploiement SSH!",
|
||||
"en": "SSH deployment tools!",
|
||||
},
|
||||
"SSH - Check connection": {
|
||||
"fr": "SSH - Vérifier la connexion",
|
||||
"en": "SSH - Check connection",
|
||||
},
|
||||
"SSH - Sync files (rsync)": {
|
||||
"fr": "SSH - Synchroniser les fichiers (rsync)",
|
||||
"en": "SSH - Sync files (rsync)",
|
||||
},
|
||||
"SSH - Install ERPLibre": {
|
||||
"fr": "SSH - Installer ERPLibre",
|
||||
"en": "SSH - Install ERPLibre",
|
||||
},
|
||||
"SSH - Start Odoo": {
|
||||
"fr": "SSH - Démarrer Odoo",
|
||||
"en": "SSH - Start Odoo",
|
||||
},
|
||||
"SSH - Stop Odoo": {
|
||||
"fr": "SSH - Arrêter Odoo",
|
||||
"en": "SSH - Stop Odoo",
|
||||
},
|
||||
"SSH - Restart Odoo": {
|
||||
"fr": "SSH - Redémarrer Odoo",
|
||||
"en": "SSH - Restart Odoo",
|
||||
},
|
||||
"SSH - Service status": {
|
||||
"fr": "SSH - Statut du service",
|
||||
"en": "SSH - Service status",
|
||||
},
|
||||
"SSH - View logs": {
|
||||
"fr": "SSH - Voir les logs",
|
||||
"en": "SSH - View logs",
|
||||
},
|
||||
"SSH - Run make target": {
|
||||
"fr": "SSH - Exécuter une cible make",
|
||||
"en": "SSH - Run make target",
|
||||
},
|
||||
"SSH - Install systemd service": {
|
||||
"fr": "SSH - Installer le service systemd",
|
||||
"en": "SSH - Install systemd service",
|
||||
},
|
||||
"SSH - Configure nginx + SSL": {
|
||||
"fr": "SSH - Configurer nginx + SSL",
|
||||
"en": "SSH - Configure nginx + SSL",
|
||||
},
|
||||
"Remote host (user@hostname or hostname): ": {
|
||||
"fr": "Hôte distant (user@hostname ou hostname) : ",
|
||||
"en": "Remote host (user@hostname or hostname): ",
|
||||
},
|
||||
"SSH user (default: erplibre): ": {
|
||||
"fr": "Utilisateur SSH (défaut: erplibre) : ",
|
||||
"en": "SSH user (default: erplibre): ",
|
||||
},
|
||||
"SSH port (default: 22): ": {
|
||||
"fr": "Port SSH (défaut: 22) : ",
|
||||
"en": "SSH port (default: 22): ",
|
||||
},
|
||||
"SSH key path (default: ~/.ssh/id_rsa, empty for none): ": {
|
||||
"fr": "Chemin de la clé SSH (défaut: ~/.ssh/id_rsa, vide pour aucune) : ",
|
||||
"en": "SSH key path (default: ~/.ssh/id_rsa, empty for none): ",
|
||||
},
|
||||
"Remote path (default: ~/erplibre_deploy_2): ": {
|
||||
"fr": "Chemin distant (défaut: ~/erplibre_deploy_2) : ",
|
||||
"en": "Remote path (default: ~/erplibre_deploy_2): ",
|
||||
},
|
||||
"Make target to run remotely: ": {
|
||||
"fr": "Cible make à exécuter à distance : ",
|
||||
"en": "Make target to run remotely: ",
|
||||
},
|
||||
"Domain name (e.g.: example.com): ": {
|
||||
"fr": "Nom de domaine (ex: example.com) : ",
|
||||
"en": "Domain name (e.g.: example.com): ",
|
||||
},
|
||||
"Admin email for SSL certificate: ": {
|
||||
"fr": "Email administrateur pour le certificat SSL : ",
|
||||
"en": "Admin email for SSL certificate: ",
|
||||
},
|
||||
"SSH host is required!": {
|
||||
"fr": "L'hôte SSH est requis!",
|
||||
"en": "SSH host is required!",
|
||||
},
|
||||
# Language selection
|
||||
"Choose language / Choisir la langue": {
|
||||
"fr": "Choisir la langue / Choose language",
|
||||
|
|
|
|||
Loading…
Reference in a new issue