From c538fdb9c27b3960367ba36d99abd6346a06007c Mon Sep 17 00:00:00 2001 From: admtracksteel Date: Tue, 21 Apr 2026 20:42:48 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Auto-deploy:=20BotVPS=20atualiza?= =?UTF-8?q?do=20em=2021/04/2026=2020:42:48?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy_manager.py | 63 +++++++++++++++++++++++++++++ ecosystem.config.js | 7 ++++ tools_v2.py | 20 ++++++++++ watchdog.py | 96 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 186 insertions(+) create mode 100644 deploy_manager.py create mode 100644 watchdog.py diff --git a/deploy_manager.py b/deploy_manager.py new file mode 100644 index 0000000..483d8b0 --- /dev/null +++ b/deploy_manager.py @@ -0,0 +1,63 @@ +import os +import subprocess +import shutil +from typing import Dict + +class DeployManager: + BASE_APPS_DIR = "/root/Apps" + + @staticmethod + def run_command(command: str, cwd: str = None): + result = subprocess.run(command, shell=True, capture_output=True, text=True, cwd=cwd) + return { + "success": result.returncode == 0, + "output": (result.stdout or result.stderr).strip() + } + + def magic_deploy(self, git_url: str) -> str: + if not git_url.endswith(".git"): + return "Erro: URL do repositório inválida (deve terminar em .git)." + + repo_name = git_url.split("/")[-1].replace(".git", "") + target_dir = os.path.join(self.BASE_APPS_DIR, repo_name) + + if os.path.exists(target_dir): + return f"Erro: O diretório {target_dir} já existe. Use /bash git pull se quiser atualizar." + + # 1. Clone + print(f"[DEPLOY] Clonando {git_url}...") + clone_res = self.run_command(f"git clone {git_url} {target_dir}") + if not clone_res["success"]: + return f"Falha no clone: {clone_res['output']}" + + # 2. Detecção de Stack + files = os.listdir(target_dir) + strategy = "" + + if "docker-compose.yml" in files or "docker-compose.yaml" in files: + strategy = "Docker Compose" + deploy_res = self.run_command("docker compose up -d", cwd=target_dir) + elif "package.json" in files: + strategy = "Node.js (PM2)" + self.run_command("npm install", cwd=target_dir) + deploy_res = self.run_command(f"pm2 start index.js --name {repo_name}", cwd=target_dir) + elif "requirements.txt" in files: + strategy = "Python (PM2/Gunicorn)" + self.run_command("pip install -r requirements.txt", cwd=target_dir) + # Tenta encontrar o arquivo principal + main_file = "main.py" if "main.py" in files else ("app.py" if "app.py" in files else None) + if main_file: + deploy_res = self.run_command(f"pm2 start {main_file} --name {repo_name}", cwd=target_dir) + else: + return f"Repositório clonado ({strategy}), mas não encontrei main.py ou app.py para iniciar." + else: + return f"Repositório clonado em {target_dir}, mas não detectei uma stack automática (Docker, Node ou Python)." + + if deploy_res["success"]: + return f"✅ **Deploy Mágico Concluído!**\n- **Repo:** {repo_name}\n- **Estratégia:** {strategy}\n- **Status:** Sucesso" + else: + return f"❌ **Falha no Deploy ({strategy}):**\n{deploy_res['output']}" + +if __name__ == "__main__": + # Teste rápido + pass diff --git a/ecosystem.config.js b/ecosystem.config.js index 5364725..fa93541 100644 --- a/ecosystem.config.js +++ b/ecosystem.config.js @@ -17,6 +17,13 @@ module.exports = { interpreter: "python3", cwd: "/root/Apps/BotVPS", restart_delay: 3000 + }, + { + name: "watchdog-vps", + script: "watchdog.py", + interpreter: "python3", + cwd: "/root/Apps/BotVPS", + restart_delay: 5000 } ] }; diff --git a/tools_v2.py b/tools_v2.py index d53817b..164684f 100644 --- a/tools_v2.py +++ b/tools_v2.py @@ -7,6 +7,7 @@ from credential_manager import ( gitea_api_url, gitea_token, supabase_url, supabase_anon_key, supabase_service_role_key ) +from deploy_manager import DeployManager # ============================================================ # UTILS @@ -138,6 +139,16 @@ class SystemTools: except Exception as e: return f"Erro ao listar {path}: {e}" + @staticmethod + def pm2_status() -> str: + """Lista todos os processos gerenciados pelo PM2.""" + return run_bash("pm2 jlist")["output"] + + @staticmethod + def pm2_restart(name: str) -> str: + """Reinicia um processo no PM2 pelo nome ou ID.""" + return run_bash(f"pm2 restart {name}")["output"] + # ============================================================ # WORKSPACE TOOLS (GWS CLI) # ============================================================ @@ -148,6 +159,12 @@ class WorkspaceTools: """Executa um comando gws completo (ex: gws-mr gmail +send ...).""" return run_bash(cmd)["output"] + @staticmethod + def magic_deploy(git_url: str) -> str: + """Clona um repositório git e tenta realizar o deploy automático (Docker/Node/Python).""" + dm = DeployManager() + return dm.magic_deploy(git_url) + # ============================================================ # REGISTRY # ============================================================ @@ -172,6 +189,9 @@ TOOLS_V2 = { "read_file": {"desc": "Lê conteúdo de um arquivo", "func": SystemTools.read_file, "danger": "safe"}, "write_file": {"desc": "Cria ou edita arquivo (caminho|conteúdo)", "func": SystemTools.write_file, "danger": "dangerous"}, "ls": {"desc": "Lista arquivos num diretório", "func": SystemTools.list_dir, "danger": "safe"}, + "pm2_status": {"desc": "Status dos processos PM2", "func": SystemTools.pm2_status, "danger": "safe"}, + "pm2_restart": {"desc": "Reiniciar processo PM2", "func": SystemTools.pm2_restart, "danger": "medium"}, + "magic_deploy": {"desc": "Deploy automático via URL Git", "func": WorkspaceTools.magic_deploy, "danger": "dangerous"}, # Google Workspace "gws": {"desc": "Executa comando GWS CLI (ex: gws-mr drive files list)", "func": WorkspaceTools.gws_command, "danger": "medium"}, diff --git a/watchdog.py b/watchdog.py new file mode 100644 index 0000000..c27cb8b --- /dev/null +++ b/watchdog.py @@ -0,0 +1,96 @@ +import os +import psutil +import time +import json +import httpx +import subprocess +from dotenv import load_dotenv + +load_dotenv() + +TOKEN = os.getenv("TELEGRAM_BOT_TOKEN") +CHAT_ID = os.getenv("TELEGRAM_CHAT_ID") + +# Configurações do Watchdog +CPU_THRESHOLD = 90.0 +CPU_STREAK_LIMIT = 6 # 6 * 10s = 60s +CHECK_INTERVAL = 10 # segundos + +class Watchdog: + def __init__(self): + self.cpu_streak = 0 + self.last_alert_time = 0 + self.alert_cooldown = 300 # 5 minutos entre alertas do mesmo tipo + + async def send_telegram_message(self, message: str): + if not TOKEN or not CHAT_ID: + print("[WATCHDOG] Erro: TOKEN ou CHAT_ID não configurados.") + return + + url = f"https://api.telegram.org/bot{TOKEN}/sendMessage" + payload = { + "chat_id": CHAT_ID, + "text": f"🚨 **[WATCHDOG VPS]**\n\n{message}", + "parse_mode": "Markdown" + } + + try: + async with httpx.AsyncClient() as client: + await client.post(url, json=payload) + except Exception as e: + print(f"[WATCHDOG] Erro ao enviar Telegram: {e}") + + def get_pm2_status(self): + try: + result = subprocess.run(["pm2", "jlist"], capture_output=True, text=True) + if result.returncode == 0: + data = json.loads(result.stdout) + issues = [] + for proc in data: + if proc['pm2_env']['status'] != 'online': + issues.append(f"🔴 App '{proc['name']}' está {proc['pm2_env']['status']}!") + return issues + except Exception as e: + print(f"[WATCHDOG] Erro PM2: {e}") + return [] + + async def run(self): + print("[WATCHDOG] Iniciado. Vigilância ativa...") + + while True: + try: + # 1. Monitoramento de CPU + cpu_usage = psutil.cpu_percent(interval=1) + if cpu_usage > CPU_THRESHOLD: + self.cpu_streak += 1 + else: + self.cpu_streak = 0 + + if self.cpu_streak >= CPU_STREAK_LIMIT: + if time.time() - self.last_alert_time > self.alert_cooldown: + await self.send_telegram_message( + f"CPU em nível crítico: {cpu_usage}% por mais de 1 minuto!" + ) + self.last_alert_time = time.time() + + # 2. Monitoramento de PM2 + pm2_issues = self.get_pm2_status() + if pm2_issues: + await self.send_telegram_message("\n".join(pm2_issues)) + + # 3. Monitoramento de Espaço em Disco + disk = psutil.disk_usage('/') + if disk.percent > 95: + if time.time() - self.last_alert_time > self.alert_cooldown: + await self.send_telegram_message(f"Espaço em disco crítico: {disk.percent}% ocupado!") + self.last_alert_time = time.time() + + await asyncio.sleep(CHECK_INTERVAL) + except Exception as e: + print(f"[WATCHDOG] Erro no loop: {e}") + await asyncio.sleep(CHECK_INTERVAL) + +if __name__ == "__main__": + import asyncio + dog = Watchdog() + asyncio.run(dog.run())