#!/usr/bin/env python3 """ TZZR HST App - Gestión del servidor HST (72.62.2.84) Servicios: Directus (3 instancias), Servidor de imágenes, APIs """ import subprocess import json import sys from dataclasses import dataclass from typing import List, Dict SERVER = "root@72.62.2.84" SSH_KEY = "~/.ssh/tzzr" # Servicios conocidos en HST SERVICES = { # Directus instances "directus_hst": {"port": 8055, "type": "cms", "desc": "Directus HST principal", "url": "hst.tzrtech.org"}, "directus_lumalia": {"port": 8056, "type": "cms", "desc": "Directus Lumalia", "url": "lumalia.tzrtech.org"}, "directus_personal": {"port": 8057, "type": "cms", "desc": "Directus Personal", "url": "personal.tzrtech.org"}, # APIs "hst-api": {"port": 5001, "type": "api", "desc": "HST Flask API"}, "hst-images": {"port": 80, "type": "web", "desc": "Servidor NGINX imágenes", "url": "tzrtech.org"}, # Infraestructura "postgres_hst": {"port": 5432, "type": "db", "desc": "PostgreSQL 15"}, "filebrowser": {"port": 8081, "type": "app", "desc": "File Browser"}, } @dataclass class Result: success: bool data: any error: str = "" def ssh(cmd: str, timeout: int = 60) -> Result: """Ejecuta comando en HST""" 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, {}) status_result = ssh(f"docker ps --filter name={service} --format '{{{{.Status}}}}'") health = "unknown" if info.get("port") and info.get("type") in ["cms", "api"]: if info.get("type") == "cms": health_result = ssh(f"curl -s http://localhost:{info.get('port')}/server/health 2>/dev/null | head -c 50") else: health_result = ssh(f"curl -s http://localhost:{info.get('port')}/health 2>/dev/null | head -c 50") health = "healthy" if health_result.success and health_result.data else "unhealthy" return { "service": service, "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""" return ssh(f"docker restart {service}") def get_logs(service: str, lines: int = 100) -> str: """Obtiene logs de un servicio""" result = ssh(f"docker logs {service} --tail {lines} 2>&1") return result.data if result.success else result.error 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 query_directus(instance: str, endpoint: str, token: str = None) -> Result: """Hace petición a una instancia de Directus""" info = SERVICES.get(instance) if not info or info.get("type") != "cms": return Result(success=False, data="", error="Instancia Directus no encontrada") port = info["port"] auth = f"-H 'Authorization: Bearer {token}'" if token else "" cmd = f"curl -s {auth} 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_directus_collections(instance: str) -> List[str]: """Lista colecciones de una instancia Directus""" result = query_directus(instance, "/collections") if result.success and isinstance(result.data, dict): collections = result.data.get("data", []) return [c.get("collection") for c in collections if not c.get("collection", "").startswith("directus_")] return [] def list_images(path: str = "/var/www/images") -> List[str]: """Lista imágenes en el servidor""" result = ssh(f"ls -la {path} 2>/dev/null | head -20") return result.data.split('\n') if result.success else [] def get_postgres_databases() -> List[str]: """Lista bases de datos PostgreSQL""" result = ssh("docker exec postgres_hst psql -U postgres -c '\\l' -t 2>/dev/null") if not result.success: return [] databases = [] for line in result.data.split('\n'): if '|' in line: db = line.split('|')[0].strip() if db and db not in ['template0', 'template1']: databases.append(db) return databases # CLI def main(): if len(sys.argv) < 2: print(""" TZZR HST App - Servidor 72.62.2.84 ================================== 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 Directus (CMS): directus Estado de las 3 instancias collections Lista colecciones de instancia query Query a Directus API Imágenes: images [path] Lista imágenes Bases de Datos: postgres Lista bases de datos Servicios disponibles: CMS: directus_hst, directus_lumalia, directus_personal API: hst-api Web: hst-images DB: postgres_hst App: filebrowser """) return cmd = sys.argv[1] if cmd == "status": print("\n📊 HST Status (72.62.2.84)") 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 HST ({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" 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 == "directus": print("\n📚 Instancias Directus en HST:") for instance in ["directus_hst", "directus_lumalia", "directus_personal"]: status = get_service_status(instance) icon = "✅" if "Up" in status["status"] else "❌" health = f"({status['health']})" if status['health'] != "unknown" else "" url = f"https://{status['url']}" if status.get('url') else "" print(f" {icon} {instance}: {status['status'][:20]} {health}") print(f" └─ {url}") elif cmd == "collections" and len(sys.argv) >= 3: collections = get_directus_collections(sys.argv[2]) print(f"\n📋 Colecciones en {sys.argv[2]}:") for c in collections: print(f" • {c}") elif cmd == "query" and len(sys.argv) >= 4: result = query_directus(sys.argv[2], sys.argv[3]) if result.success: print(json.dumps(result.data, indent=2)[:2000]) else: print(f"❌ Error: {result.error}") elif cmd == "images": path = sys.argv[2] if len(sys.argv) > 2 else "/var/www/images" images = list_images(path) print(f"\n🖼️ Imágenes en {path}:") for img in images[:20]: print(f" {img}") elif cmd == "postgres": dbs = get_postgres_databases() print("\n🐘 Bases de datos PostgreSQL:") for db in dbs: print(f" • {db}") else: print("❌ Comando no reconocido. Usa 'python app.py' para ver ayuda.") if __name__ == "__main__": main()