diff --git a/syncra_welcome/__init__.py b/syncra_welcome/__init__.py new file mode 100644 index 0000000..9b42961 --- /dev/null +++ b/syncra_welcome/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/syncra_welcome/__manifest__.py b/syncra_welcome/__manifest__.py new file mode 100644 index 0000000..f1930b4 --- /dev/null +++ b/syncra_welcome/__manifest__.py @@ -0,0 +1,28 @@ +{ + 'name': 'SYNCRA Welcome', + 'version': '1.1', + 'category': 'Tools', + 'summary': 'Módulo DevOps SYNCRA para Gestão de Módulos e Logs', + 'description': """ + Módulo SYNCRA para automação de infraestrutura. + + Funcionalidades: + - Painel de Boas-Vindas com atalhos para Gitea e Cockpit. + - Automação de Deploy: Extração de ZIP e Git Push automático. + - Gestão de Logs: Visualização e Download do log oficial da VPS HilariBD. + """, + 'author': 'Gelson Lírio', + 'depends': [ + 'base', + ], + 'data': [ + 'security/ir.model.access.csv', + 'views/syncra_welcome_views.xml', + 'views/syncra_devops_views.xml', + 'views/syncra_logs_views.xml', + ], + 'installable': True, + 'application': True, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/syncra_welcome/models/__init__.py b/syncra_welcome/models/__init__.py new file mode 100644 index 0000000..e5c7403 --- /dev/null +++ b/syncra_welcome/models/__init__.py @@ -0,0 +1,3 @@ +from . import syncra_welcome +from . import syncra_devops +from . import syncra_logs diff --git a/syncra_welcome/models/syncra_devops.py b/syncra_welcome/models/syncra_devops.py new file mode 100644 index 0000000..ad0e3ec --- /dev/null +++ b/syncra_welcome/models/syncra_devops.py @@ -0,0 +1,79 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError +import os, zipfile, base64, io, subprocess + +class SyncraDevOps(models.Model): + _inherit = 'syncra.welcome' + + module_zip = fields.Binary(string="Upload Módulo (.zip)") + module_filename = fields.Char(string="Nome do Ficheiro") + + def action_deploy_module(self): + """Descompacta o ZIP na custom_addons2 e sincroniza com o Gitea do gelson.souto""" + if not self.module_zip: + raise UserError(_("Por favor, carregue um ficheiro .zip primeiro.")) + + addons_path = '/root/odoo-18.0+e.20251216/custom_addons2/' + # URL exato validado no terminal da HilariBD + git_url = "http://gelson.souto:Luanda244@173.208.243.178:3000/gelson.souto/syncra_addons.git" + + try: + # 1. Descompactar os ficheiros + zip_data = base64.b64decode(self.module_zip) + with zipfile.ZipFile(io.BytesIO(zip_data)) as z: + z.extractall(addons_path) + + # 2. Operações Git + os.chdir(addons_path) + + # Configurações de identidade local + subprocess.run(['git', 'config', 'user.email', 'gelson.souto@syncra.com'], check=True) + subprocess.run(['git', 'config', 'user.name', 'Gelson do Souto'], check=True) + + # Adicionar e Commit (com verificação para não falhar se não houver mudanças) + subprocess.run(['git', 'add', '.'], check=True) + + # O status verifica se há algo novo antes de tentar o commit + status = subprocess.run(['git', 'status', '--porcelain'], capture_output=True, text=True) + if status.stdout: + subprocess.run(['git', 'commit', '-m', f"Auto-deploy SYNCRA: {self.module_filename}"], check=True) + + # 3. Push Direto e Seguro + # Usamos -c credential.helper= para garantir que ele ignore senhas antigas e use o git_url + subprocess.run(['git', '-c', 'credential.helper=', 'push', git_url, 'HEAD:main'], check=True) + + # 4. Atualizar lista de módulos no Odoo + self.env['ir.module.module'].update_list() + self.module_zip = False + + return { + 'effect': { + 'fadeout': 'slow', + 'message': 'Sucesso! Módulo em custom_addons2 e Gitea atualizado.', + 'type': 'rainbow_man' + } + } + + except subprocess.CalledProcessError as e: + raise UserError(_("Erro no comando Git (Verifique o terminal): %s") % (e.stderr or str(e))) + except Exception as e: + raise UserError(_("Erro inesperado no Deploy: %s") % str(e)) + + def action_restart_odoo(self): + """Reinicia o serviço sem causar erro de SIGTERM no ecrã""" + try: + # O comando 'sleep 1' dá tempo ao Odoo para enviar o Rainbow Man antes de cair + restart_command = "sleep 1 && sudo systemctl restart odoo" + + # Executa de forma desvinculada (nohup ou fork) + subprocess.Popen(['/bin/bash', '-c', restart_command]) + + return { + 'effect': { + 'fadeout': 'slow', + 'message': 'Sinal de reinício enviado! O sistema voltará em instantes.', + 'type': 'rainbow_man', + } + } + except Exception as e: + raise UserError(_("Falha ao agendar reinício: %s") % str(e)) diff --git a/syncra_welcome/models/syncra_logs.py b/syncra_welcome/models/syncra_logs.py new file mode 100644 index 0000000..eaed24c --- /dev/null +++ b/syncra_welcome/models/syncra_logs.py @@ -0,0 +1,47 @@ +from odoo import models, fields, api, _ +import os, base64 + +class SyncraLogs(models.Model): + _inherit = 'syncra.welcome' + + system_logs = fields.Text(string="Logs do Odoo", readonly=True) + last_log_file = fields.Binary(string="Ficheiro de Log", readonly=True) + last_log_filename = fields.Char(string="Nome do Ficheiro de Log") + + def action_fetch_logs(self): + """Lê as últimas linhas do log oficial com otimização de memória""" + log_path = '/var/log/odoo/odoo.log' + try: + if os.path.exists(log_path): + # Usamos um comando de sistema (tail) que é muito mais rápido para arquivos grandes + import subprocess + result = subprocess.run(['tail', '-n', '100', log_path], capture_output=True, text=True) + logs = result.stdout + + if not logs: + self.system_logs = "O ficheiro existe mas está vazio." + else: + self.system_logs = logs + else: + self.system_logs = f"Log não encontrado em: {log_path}" + except Exception as e: + self.system_logs = f"Erro ao aceder aos logs: {str(e)}" + + def action_download_last_log(self): + """Gera o download sem comprometer a memória do servidor""" + log_path = '/var/log/odoo/odoo.log' + if os.path.exists(log_path): + with open(log_path, 'rb') as f: + # O Odoo lida bem com base64 para arquivos de texto + log_content = base64.b64encode(f.read()) + + self.write({ + 'last_log_file': log_content, + 'last_log_filename': f"syncra_vps_log_{fields.Date.today()}.txt" + }) + + return { + 'type': 'ir.actions.act_url', + 'url': f'/web/content/?model={self._name}&id={self.id}&field=last_log_file&filename={self.last_log_filename}&download=true', + 'target': 'self', + } diff --git a/syncra_welcome/models/syncra_welcome.py b/syncra_welcome/models/syncra_welcome.py new file mode 100644 index 0000000..864b2a5 --- /dev/null +++ b/syncra_welcome/models/syncra_welcome.py @@ -0,0 +1,33 @@ +from odoo import models, fields + +class SyncraWelcome(models.Model): + _name = 'syncra.welcome' + _description = 'Portal de Boas-Vindas SYNCRA' + + name = fields.Char(string="Nome do Utilizador", required=True) + welcome_message = fields.Text(string="Mensagem", default="Bem-vindo ao ecossistema SYNCRA!") + + # Adicionando os campos para o Deploy não quebrar a vista + module_zip = fields.Binary(string="Upload Módulo (.zip)") + module_filename = fields.Char(string="Nome do Ficheiro") + + def action_open_gitea(self): + return { + 'type': 'ir.actions.act_url', + 'url': 'http://173.208.243.178:3000', + 'target': 'new', + } + + def action_open_cockpit(self): + return { + 'type': 'ir.actions.act_url', + 'url': 'https://173.208.243.178:9090', + 'target': 'new', + } + + def action_open_netdata(self): + return { + 'type': 'ir.actions.act_url', + 'url': 'http://173.208.243.178:8080/', + 'target': 'new', + } diff --git a/syncra_welcome/security/ir.model.access.csv b/syncra_welcome/security/ir.model.access.csv new file mode 100644 index 0000000..53fc2f1 --- /dev/null +++ b/syncra_welcome/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_syncra_welcome_all,syncra.welcome.all,model_syncra_welcome,,1,1,1,1 diff --git a/syncra_welcome/views/syncra_devops_views.xml b/syncra_welcome/views/syncra_devops_views.xml new file mode 100644 index 0000000..b3a0ba3 --- /dev/null +++ b/syncra_welcome/views/syncra_devops_views.xml @@ -0,0 +1,45 @@ + + + + syncra.welcome.form.devops + syncra.welcome + + + + + + + + + + + + + + + + + + A descompactação automática será feita no diretório: + /root/odoo-18.0+e.20251216/custom_addons2/ + + + + Nota: Após o deploy bem-sucedido, utilize o botão + "Reiniciar Servidor" no topo para aplicar as mudanças de código Python. + + + + + + + diff --git a/syncra_welcome/views/syncra_logs_views.xml b/syncra_welcome/views/syncra_logs_views.xml new file mode 100644 index 0000000..72732a2 --- /dev/null +++ b/syncra_welcome/views/syncra_logs_views.xml @@ -0,0 +1,24 @@ + + + + syncra.welcome.form.logs + syncra.welcome + + + + + + + + + + + + + + + + + diff --git a/syncra_welcome/views/syncra_welcome_views.xml b/syncra_welcome/views/syncra_welcome_views.xml new file mode 100644 index 0000000..f51d629 --- /dev/null +++ b/syncra_welcome/views/syncra_welcome_views.xml @@ -0,0 +1,48 @@ + + + + Bem-vindo ao SYNCRA + syncra.welcome + list,form + + + + syncra.welcome.list + syncra.welcome + + + + + + + + + + syncra.welcome.form + syncra.welcome + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/syncra_welcome/wizard/__init__.py b/syncra_welcome/wizard/__init__.py new file mode 100644 index 0000000..3349918 --- /dev/null +++ b/syncra_welcome/wizard/__init__.py @@ -0,0 +1,2 @@ +from . import syncra_deploy_wizardy + diff --git a/syncra_welcome/wizard/syncra_deploy_wizard.py b/syncra_welcome/wizard/syncra_deploy_wizard.py new file mode 100644 index 0000000..3f82956 --- /dev/null +++ b/syncra_welcome/wizard/syncra_deploy_wizard.py @@ -0,0 +1,9 @@ +class SyncraDeployWizard(models.TransientModel): + _name = 'syncra.deploy.wizard' + _description = 'Assistente de Deploy SYNCRA' + + welcome_id = fields.Many2one('syncra.welcome', string="Origem") + commit_message = fields.Char(string="Mensagem de Commit", required=True, default="Update modulo via SYNCRA") + + def action_confirm_deploy(self): + return self.welcome_id.with_context(commit_msg=self.commit_message).action_deploy_module() diff --git a/syncra_welcome/wizard/syncra_deploy_wizard.xml b/syncra_welcome/wizard/syncra_deploy_wizard.xml new file mode 100644 index 0000000..817afff --- /dev/null +++ b/syncra_welcome/wizard/syncra_deploy_wizard.xml @@ -0,0 +1,15 @@ + + syncra.deploy.wizard.form + syncra.deploy.wizard + + + + + + + + +
/root/odoo-18.0+e.20251216/custom_addons2/
+ Nota: Após o deploy bem-sucedido, utilize o botão + "Reiniciar Servidor" no topo para aplicar as mudanças de código Python. +