#!/usr/bin/env python3 """ TZZR CORP App - Gestión del servidor CORP (92.112.181.188) Servicios: Agentes Margaret/Jared/Mason/Feldman, Context Manager, Apps """ import subprocess import json import sys from dataclasses import dataclass from typing import List, Dict, Optional from datetime import datetime SERVER = "root@92.112.181.188" SSH_KEY = "~/.ssh/tzzr" # Servicios conocidos en CORP SERVICES = { # Microservicios TZZR "margaret": {"port": 5051, "type": "service", "desc": "Recepción de datos", "path": "/opt/margaret"}, "jared": {"port": 5052, "type": "service", "desc": "Automatización de flujos", "path": "/opt/jared"}, "mason": {"port": 5053, "type": "service", "desc": "Espacio de enriquecimiento", "path": "/opt/mason"}, "feldman": {"port": 5054, "type": "service", "desc": "Registro final + Merkle", "path": "/opt/feldman"}, # Aplicaciones "nextcloud": {"port": 8080, "type": "app", "desc": "Cloud storage", "url": "nextcloud.tzzrcorp.me"}, "directus": {"port": 8055, "type": "app", "desc": "CMS", "url": "tzzrcorp.me"}, "vaultwarden": {"port": 8081, "type": "app", "desc": "Passwords", "url": "vault.tzzrcorp.me"}, "shlink": {"port": 8082, "type": "app", "desc": "URL shortener", "url": "shlink.tzzrcorp.me"}, "addy": {"port": 8083, "type": "app", "desc": "Email aliases", "url": "addy.tzzrcorp.me"}, "odoo": {"port": 8069, "type": "app", "desc": "ERP", "url": "erp.tzzrcorp.me"}, "ntfy": {"port": 8880, "type": "app", "desc": "Notifications", "url": "ntfy.tzzrcorp.me"}, # APIs y Automatización "hsu": {"port": 5002, "type": "api", "desc": "HSU User Library", "url": "hsu.tzzrcorp.me"}, "windmill": {"port": 8000, "type": "automation", "desc": "Automatización de flujos", "url": "windmill.tzzrcorp.me"}, "context-manager": {"port": None, "type": "system", "desc": "Context Manager IA", "path": "/opt/context-manager"}, # Infraestructura "postgres": {"port": 5432, "type": "db", "desc": "PostgreSQL 16"}, "redis": {"port": 6379, "type": "db", "desc": "Cache Redis"}, } @dataclass class Result: success: bool data: any error: str = "" def ssh(cmd: str, timeout: int = 60) -> Result: """Ejecuta comando en CORP""" full_cmd = f'ssh -i {SSH_KEY} {SERVER} "{cmd}"' try: result = subprocess.run(full_cmd, shell=True, capture_output=True, text=True, timeout=timeout) return Result(success=result.returncode == 0, data=result.stdout.strip(), error=result.stderr.strip()) except Exception as e: return Result(success=False, data="", error=str(e)) def get_all_containers() -> List[Dict]: """Lista todos los contenedores Docker""" result = ssh("docker ps -a --format '{{.Names}}|{{.Status}}|{{.Ports}}|{{.Image}}'") if not result.success: return [] containers = [] for line in result.data.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 "", "image": parts[3] if len(parts) > 3 else "" }) return containers def get_service_status(service: str) -> Dict: """Estado detallado de un servicio""" info = SERVICES.get(service, {}) container = f"{service}-service" if info.get("type") == "agent" else service status_result = ssh(f"docker ps --filter name={container} --format '{{{{.Status}}}}'") health = "unknown" if info.get("port"): health_result = ssh(f"curl -s http://localhost:{info.get('port')}/health 2>/dev/null | head -c 100") health = "healthy" if health_result.success and health_result.data else "unhealthy" return { "service": service, "container": container, "status": status_result.data if status_result.success else "not found", "health": health, "port": info.get("port"), "url": info.get("url"), "desc": info.get("desc") } def restart_service(service: str) -> Result: """Reinicia un servicio""" info = SERVICES.get(service, {}) container = f"{service}-service" if info.get("type") == "agent" else service return ssh(f"docker restart {container}") def get_logs(service: str, lines: int = 100) -> str: """Obtiene logs de un servicio""" info = SERVICES.get(service, {}) container = f"{service}-service" if info.get("type") == "agent" else service result = ssh(f"docker logs {container} --tail {lines} 2>&1") return result.data if result.success else result.error def query_agent(agent: str, endpoint: str, method: str = "GET", data: dict = None) -> Result: """Hace petición a un agente TZZR""" info = SERVICES.get(agent) if not info or info.get("type") != "agent": return Result(success=False, data="", error="Agente no encontrado") port = info["port"] if method == "GET": cmd = f"curl -s http://localhost:{port}{endpoint}" else: json_data = json.dumps(data) if data else "{}" cmd = f"curl -s -X {method} -H 'Content-Type: application/json' -d '{json_data}' http://localhost:{port}{endpoint}" result = ssh(cmd) try: return Result(success=result.success, data=json.loads(result.data) if result.data else {}) except: return Result(success=result.success, data=result.data) def get_system_stats() -> Dict: """Estadísticas del sistema""" stats = {} mem_result = ssh("free -h | grep Mem | awk '{print $2,$3,$4}'") if mem_result.success: parts = mem_result.data.split() stats["memory"] = {"total": parts[0], "used": parts[1], "available": parts[2]} disk_result = ssh("df -h / | tail -1 | awk '{print $2,$3,$4,$5}'") if disk_result.success: parts = disk_result.data.split() stats["disk"] = {"total": parts[0], "used": parts[1], "available": parts[2], "percent": parts[3]} containers_result = ssh("docker ps -q | wc -l") stats["containers_running"] = int(containers_result.data) if containers_result.success else 0 return stats def get_postgres_stats() -> Dict: """Estadísticas de PostgreSQL""" result = ssh("sudo -u postgres psql -c \"SELECT datname, pg_size_pretty(pg_database_size(datname)) as size FROM pg_database WHERE datistemplate = false;\" -t") databases = {} if result.success: for line in result.data.split('\n'): if '|' in line: parts = line.split('|') databases[parts[0].strip()] = parts[1].strip() return databases def ingest_to_margaret(contenedor: dict, auth_key: str) -> Result: """Envía datos a Margaret para ingestión""" json_data = json.dumps(contenedor) cmd = f"curl -s -X POST -H 'Content-Type: application/json' -H 'X-Auth-Key: {auth_key}' -d '{json_data}' http://localhost:5051/ingest" result = ssh(cmd) try: return Result(success=result.success, data=json.loads(result.data) if result.data else {}) except: return Result(success=result.success, data=result.data) def execute_flow(flow_id: int, data: dict, auth_key: str) -> Result: """Ejecuta un flujo en Jared""" json_data = json.dumps(data) cmd = f"curl -s -X POST -H 'Content-Type: application/json' -H 'X-Auth-Key: {auth_key}' -d '{json_data}' http://localhost:5052/ejecutar/{flow_id}" result = ssh(cmd) try: return Result(success=result.success, data=json.loads(result.data) if result.data else {}) except: return Result(success=result.success, data=result.data) def get_pending_issues() -> Result: """Obtiene incidencias pendientes de Mason""" result = ssh("curl -s http://localhost:5053/pendientes") try: return Result(success=result.success, data=json.loads(result.data) if result.data else []) except: return Result(success=result.success, data=result.data) def verify_merkle(hash: str) -> Result: """Verifica registro en Feldman""" result = ssh(f"curl -s http://localhost:5054/verify/{hash}") try: return Result(success=result.success, data=json.loads(result.data) if result.data else {}) except: return Result(success=result.success, data=result.data) # CLI def main(): if len(sys.argv) < 2: print(""" TZZR CORP App - Servidor 92.112.181.188 ======================================= Uso: python app.py [argumentos] Comandos Generales: status Estado general del sistema containers Lista todos los contenedores stats Estadísticas del sistema Gestión de Servicios: service Estado detallado de servicio restart Reiniciar servicio logs [lines] Ver logs Agentes TZZR (Flujo de datos): agents Estado de todos los agentes agent Query a agente (GET) margaret Estado de Margaret (Secretaria) jared Estado de Jared (Flujos) mason Estado de Mason (Editor) feldman Estado de Feldman (Contable) flows Lista flujos predefinidos en Jared pending Incidencias pendientes en Mason verify Verificar hash en Feldman Bases de Datos: postgres Estadísticas PostgreSQL Servicios disponibles: Agentes: margaret, jared, mason, feldman Apps: nextcloud, directus, vaultwarden, shlink, addy, odoo, ntfy APIs: hsu, context-manager DB: postgres, redis """) return cmd = sys.argv[1] if cmd == "status": print("\n📊 CORP Status (92.112.181.188)") print("=" * 40) stats = get_system_stats() print(f"💾 Memoria: {stats.get('memory', {}).get('used', '?')}/{stats.get('memory', {}).get('total', '?')}") print(f"💿 Disco: {stats.get('disk', {}).get('used', '?')}/{stats.get('disk', {}).get('total', '?')} ({stats.get('disk', {}).get('percent', '?')})") print(f"📦 Contenedores: {stats.get('containers_running', 0)}") elif cmd == "containers": containers = get_all_containers() print(f"\n📦 Contenedores en CORP ({len(containers)} total):") for c in containers: icon = "✅" if "Up" in c["status"] else "❌" print(f" {icon} {c['name']}: {c['status'][:30]}") elif cmd == "service" and len(sys.argv) >= 3: status = get_service_status(sys.argv[2]) print(f"\n🔧 {status['service']}") print(f" Container: {status['container']}") print(f" Status: {status['status']}") print(f" Health: {status['health']}") print(f" Desc: {status.get('desc', '')}") if status.get('url'): print(f" URL: https://{status['url']}") elif cmd == "restart" and len(sys.argv) >= 3: result = restart_service(sys.argv[2]) print(f"{'✅' if result.success else '❌'} {sys.argv[2]}: {'reiniciado' if result.success else result.error}") elif cmd == "logs" and len(sys.argv) >= 3: lines = int(sys.argv[3]) if len(sys.argv) > 3 else 50 print(get_logs(sys.argv[2], lines)) elif cmd == "agents": print("\n🤖 Agentes TZZR en CORP:") print(" PACKET → MARGARET → JARED → MASON/FELDMAN") print() for agent in ["margaret", "jared", "mason", "feldman"]: status = get_service_status(agent) icon = "✅" if "Up" in status["status"] else "❌" health = f"({status['health']})" if status['health'] != "unknown" else "" print(f" {icon} {agent}: {status['status'][:20]} {health}") print(f" └─ {SERVICES[agent]['desc']}") elif cmd == "agent" and len(sys.argv) >= 4: result = query_agent(sys.argv[2], sys.argv[3]) if result.success: print(json.dumps(result.data, indent=2) if isinstance(result.data, dict) else result.data) else: print(f"❌ Error: {result.error}") elif cmd in ["margaret", "jared", "mason", "feldman"]: status = get_service_status(cmd) print(f"\n🤖 {cmd.upper()} - {SERVICES[cmd]['desc']}") print(f" Puerto: {SERVICES[cmd]['port']}") print(f" Status: {status['status']}") print(f" Health: {status['health']}") # Endpoints específicos contracts = query_agent(cmd, "/s-contract") if contracts.success and isinstance(contracts.data, dict): print(f" Versión: {contracts.data.get('version', 'N/A')}") elif cmd == "flows": result = query_agent("jared", "/flujos") if result.success: flows = result.data if isinstance(result.data, list) else [] print("\n📋 Flujos predefinidos en Jared:") for f in flows: print(f" • [{f.get('id')}] {f.get('nombre')}") elif cmd == "pending": result = get_pending_issues() if result.success: issues = result.data if isinstance(result.data, list) else [] print(f"\n⚠️ Incidencias pendientes en Mason: {len(issues)}") for i in issues[:10]: print(f" • {i.get('id')}: {i.get('tipo', 'N/A')} - {i.get('descripcion', '')[:40]}") elif cmd == "verify" and len(sys.argv) >= 3: result = verify_merkle(sys.argv[2]) if result.success: print(json.dumps(result.data, indent=2)) else: print(f"❌ Error: {result.error}") elif cmd == "postgres": dbs = get_postgres_stats() print("\n🐘 PostgreSQL en CORP:") for db, size in dbs.items(): print(f" • {db}: {size}") else: print("❌ Comando no reconocido. Usa 'python app.py' para ver ayuda.") if __name__ == "__main__": main()