Files
captain-claude/apps/devops/app.py
ARCHITECT d35f11e2f7 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>
2026-01-02 01:13:30 +00:00

231 lines
7.3 KiB
Python

#!/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()