#!/usr/bin/env python3 """ TZZR DevOps App - Gestión de despliegues y construcción """ import subprocess import sys from dataclasses import dataclass from typing import Optional import json # Configuración de servidores SERVERS = { "deck": {"host": "root@72.62.1.113", "name": "DECK"}, "corp": {"host": "root@92.112.181.188", "name": "CORP"}, "hst": {"host": "root@72.62.2.84", "name": "HST"}, "local": {"host": None, "name": "LOCAL (69.62.126.110)"} } SSH_KEY = "~/.ssh/tzzr" # Agentes conocidos AGENTS = { "deck": ["clara", "alfred", "mason", "feldman"], "corp": ["margaret", "jared", "mason", "feldman"], "hst": ["hst-api", "directus_hst", "directus_lumalia", "directus_personal"] } @dataclass class CommandResult: success: bool output: str error: str = "" def ssh_cmd(server: str, command: str) -> CommandResult: """Ejecuta comando SSH en servidor remoto""" if server == "local": full_cmd = command else: host = SERVERS[server]["host"] full_cmd = f'ssh -i {SSH_KEY} {host} "{command}"' try: result = subprocess.run(full_cmd, shell=True, capture_output=True, text=True, timeout=60) return CommandResult( success=result.returncode == 0, output=result.stdout.strip(), error=result.stderr.strip() ) except subprocess.TimeoutExpired: return CommandResult(success=False, output="", error="Timeout") except Exception as e: return CommandResult(success=False, output="", error=str(e)) def deploy_agent(agent: str, server: str) -> CommandResult: """Despliega un agente en el servidor especificado""" print(f"🚀 Desplegando {agent} en {server}...") # Usar el script de deploy en DECK cmd = f"/opt/scripts/deploy-agent.sh {agent} {server}" result = ssh_cmd("deck", cmd) if result.success: print(f"✅ {agent} desplegado exitosamente en {server}") else: print(f"❌ Error desplegando {agent}: {result.error}") return result def backup_postgres(server: str = "deck") -> CommandResult: """Ejecuta backup de PostgreSQL""" print(f"💾 Ejecutando backup en {server}...") result = ssh_cmd(server, "/opt/scripts/backup_postgres.sh") if result.success: print("✅ Backup completado") else: print(f"❌ Error: {result.error}") return result def sync_r2() -> CommandResult: """Sincroniza backups a R2""" print("☁️ Sincronizando con R2...") result = ssh_cmd("deck", "/opt/scripts/sync_backups_r2.sh") if result.success: print("✅ Sincronización completada") else: print(f"❌ Error: {result.error}") return result def onboard_user(email: str, username: str) -> CommandResult: """Da de alta un nuevo usuario""" print(f"👤 Creando usuario {username} ({email})...") result = ssh_cmd("deck", f"/opt/scripts/onboard-user.sh {email} {username}") if result.success: print(f"✅ Usuario {username} creado") else: print(f"❌ Error: {result.error}") return result def get_agent_status(server: str) -> dict: """Obtiene estado de agentes en un servidor""" agents = AGENTS.get(server, []) status = {} for agent in agents: result = ssh_cmd(server, f"docker ps --filter name={agent} --format '{{{{.Status}}}}'") status[agent] = result.output if result.success else "unknown" return status def restart_agent(agent: str, server: str) -> CommandResult: """Reinicia un agente específico""" print(f"🔄 Reiniciando {agent} en {server}...") result = ssh_cmd(server, f"docker restart {agent}-service 2>/dev/null || docker restart {agent}") if result.success: print(f"✅ {agent} reiniciado") else: print(f"❌ Error: {result.error}") return result def get_logs(agent: str, server: str, lines: int = 50) -> CommandResult: """Obtiene logs de un agente""" container = f"{agent}-service" if agent in ["clara", "alfred", "mason", "feldman", "margaret", "jared"] else agent return ssh_cmd(server, f"docker logs {container} --tail {lines} 2>&1") def list_deployments() -> dict: """Lista todos los deployments activos""" deployments = {} for server in ["deck", "corp", "hst"]: result = ssh_cmd(server, "docker ps --format '{{.Names}}|{{.Status}}|{{.Ports}}'") if result.success: containers = [] for line in result.output.split('\n'): if line: parts = line.split('|') containers.append({ "name": parts[0], "status": parts[1] if len(parts) > 1 else "", "ports": parts[2] if len(parts) > 2 else "" }) deployments[server] = containers return deployments def git_pull_all(server: str) -> dict: """Hace git pull en todos los proyectos de /opt""" result = ssh_cmd(server, "for d in /opt/*/; do echo \"=== $d ===\"; cd $d && git pull 2>/dev/null || echo 'no git'; done") return {"output": result.output, "success": result.success} # CLI def main(): if len(sys.argv) < 2: print(""" TZZR DevOps App =============== Uso: python app.py [argumentos] Comandos: deploy Despliega un agente backup [server] Ejecuta backup PostgreSQL sync Sincroniza backups a R2 onboard Alta de usuario status Estado de agentes restart Reinicia agente logs Ver logs de agente list Lista todos los deployments pull Git pull en todos los proyectos Servidores: deck, corp, hst, local Agentes DECK: clara, alfred, mason, feldman Agentes CORP: margaret, jared, mason, feldman """) return cmd = sys.argv[1] if cmd == "deploy" and len(sys.argv) >= 4: deploy_agent(sys.argv[2], sys.argv[3]) elif cmd == "backup": server = sys.argv[2] if len(sys.argv) > 2 else "deck" backup_postgres(server) elif cmd == "sync": sync_r2() elif cmd == "onboard" and len(sys.argv) >= 4: onboard_user(sys.argv[2], sys.argv[3]) elif cmd == "status" and len(sys.argv) >= 3: status = get_agent_status(sys.argv[2]) print(f"\n📊 Estado de agentes en {sys.argv[2]}:") for agent, st in status.items(): icon = "✅" if "Up" in st else "❌" print(f" {icon} {agent}: {st}") elif cmd == "restart" and len(sys.argv) >= 4: restart_agent(sys.argv[2], sys.argv[3]) elif cmd == "logs" and len(sys.argv) >= 4: result = get_logs(sys.argv[2], sys.argv[3]) print(result.output) elif cmd == "list": deployments = list_deployments() for server, containers in deployments.items(): print(f"\n📦 {server.upper()}:") for c in containers[:10]: print(f" • {c['name']}: {c['status']}") if len(containers) > 10: print(f" ... y {len(containers)-10} más") elif cmd == "pull" and len(sys.argv) >= 3: result = git_pull_all(sys.argv[2]) print(result["output"]) else: print("❌ Comando no reconocido. Usa 'python app.py' para ver ayuda.") if __name__ == "__main__": main()