🚀 Auto-deploy: BotVPS atualizado em 21/04/2026 20:42:48
This commit is contained in:
63
deploy_manager.py
Normal file
63
deploy_manager.py
Normal file
@@ -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
|
||||||
@@ -17,6 +17,13 @@ module.exports = {
|
|||||||
interpreter: "python3",
|
interpreter: "python3",
|
||||||
cwd: "/root/Apps/BotVPS",
|
cwd: "/root/Apps/BotVPS",
|
||||||
restart_delay: 3000
|
restart_delay: 3000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "watchdog-vps",
|
||||||
|
script: "watchdog.py",
|
||||||
|
interpreter: "python3",
|
||||||
|
cwd: "/root/Apps/BotVPS",
|
||||||
|
restart_delay: 5000
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
20
tools_v2.py
20
tools_v2.py
@@ -7,6 +7,7 @@ from credential_manager import (
|
|||||||
gitea_api_url, gitea_token, supabase_url, supabase_anon_key,
|
gitea_api_url, gitea_token, supabase_url, supabase_anon_key,
|
||||||
supabase_service_role_key
|
supabase_service_role_key
|
||||||
)
|
)
|
||||||
|
from deploy_manager import DeployManager
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# UTILS
|
# UTILS
|
||||||
@@ -138,6 +139,16 @@ class SystemTools:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Erro ao listar {path}: {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)
|
# WORKSPACE TOOLS (GWS CLI)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -148,6 +159,12 @@ class WorkspaceTools:
|
|||||||
"""Executa um comando gws completo (ex: gws-mr gmail +send ...)."""
|
"""Executa um comando gws completo (ex: gws-mr gmail +send ...)."""
|
||||||
return run_bash(cmd)["output"]
|
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
|
# REGISTRY
|
||||||
# ============================================================
|
# ============================================================
|
||||||
@@ -172,6 +189,9 @@ TOOLS_V2 = {
|
|||||||
"read_file": {"desc": "Lê conteúdo de um arquivo", "func": SystemTools.read_file, "danger": "safe"},
|
"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"},
|
"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"},
|
"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
|
# Google Workspace
|
||||||
"gws": {"desc": "Executa comando GWS CLI (ex: gws-mr drive files list)", "func": WorkspaceTools.gws_command, "danger": "medium"},
|
"gws": {"desc": "Executa comando GWS CLI (ex: gws-mr drive files list)", "func": WorkspaceTools.gws_command, "danger": "medium"},
|
||||||
|
|||||||
96
watchdog.py
Normal file
96
watchdog.py
Normal file
@@ -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())
|
||||||
Reference in New Issue
Block a user