Add apps modules and improve captain_claude logging
- Add apps/ directory with modular components: - captain.py: Main orchestrator - corp/, deck/, devops/, docker/, hst/: Domain-specific apps - Fix duplicate logger handlers in long sessions - Add flush=True to print statements for real-time output Note: flow-ui, mindlink, tzzr-cli are separate repos (not included) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
230
apps/devops/app.py
Normal file
230
apps/devops/app.py
Normal file
@@ -0,0 +1,230 @@
|
||||
#!/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 <comando> [argumentos]
|
||||
|
||||
Comandos:
|
||||
deploy <agent> <server> Despliega un agente
|
||||
backup [server] Ejecuta backup PostgreSQL
|
||||
sync Sincroniza backups a R2
|
||||
onboard <email> <username> Alta de usuario
|
||||
status <server> Estado de agentes
|
||||
restart <agent> <server> Reinicia agente
|
||||
logs <agent> <server> Ver logs de agente
|
||||
list Lista todos los deployments
|
||||
pull <server> 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()
|
||||
Reference in New Issue
Block a user