From c4c761e408941b83a8b82513a2186dea03c477e5 Mon Sep 17 00:00:00 2001 From: Ricardo Monla Date: Sat, 7 Mar 2026 09:45:21 -0300 Subject: [PATCH] =?UTF-8?q?Refactor(ADN):=20Optimizaci=C3=B3n=20de=20la=20?= =?UTF-8?q?arquitectura=20y=20reparaci=C3=B3n=20de=20subcomandos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reestructuración del CLI en módulos discretos (core/, comando/, config/, clientes/) - Migración del punto de entrada tools/adn.rb hacia el launcher ejecutable tools/adn/run - Simplificación de nombres de comandos (remoción prefijo subcomando_) - Corrección de rutas base transversales (PROJECT_ROOT, BITACORAS_DIR) - Reescritura integral de sentencias require_relative - Restauración de sintaxis en archivo sync.rb truncado --- tools/adn/README.md | 77 +++++++ .../dtic_bitacoras_api.rb | 0 tools/adn/comando/backup.rb | 191 ++++++++++++++++++ tools/adn/{subcomando_db.rb => comando/db.rb} | 16 +- .../{subcomando_plan.rb => comando/plan.rb} | 16 +- .../{subcomando_sync.rb => comando/sync.rb} | 14 +- .../triggers.rb} | 18 +- tools/adn/{ => config}/config.yml | 0 tools/adn/{ => core}/eventos.rb | 0 tools/adn/{ => core}/logger.rb | 0 tools/adn/{ => core}/triggers.rb | 2 +- tools/adn/{ => core}/validador.rb | 0 tools/{adn.rb => adn/run} | 106 +++++----- 13 files changed, 365 insertions(+), 75 deletions(-) create mode 100644 tools/adn/README.md rename tools/adn/{clients => clientes}/dtic_bitacoras_api.rb (100%) create mode 100644 tools/adn/comando/backup.rb rename tools/adn/{subcomando_db.rb => comando/db.rb} (98%) rename tools/adn/{subcomando_plan.rb => comando/plan.rb} (96%) rename tools/adn/{subcomando_sync.rb => comando/sync.rb} (96%) rename tools/adn/{subcomando_triggers.rb => comando/triggers.rb} (95%) rename tools/adn/{ => config}/config.yml (100%) rename tools/adn/{ => core}/eventos.rb (100%) rename tools/adn/{ => core}/logger.rb (100%) rename tools/adn/{ => core}/triggers.rb (99%) rename tools/adn/{ => core}/validador.rb (100%) rename tools/{adn.rb => adn/run} (87%) mode change 100644 => 100755 diff --git a/tools/adn/README.md b/tools/adn/README.md new file mode 100644 index 00000000..0974f28a --- /dev/null +++ b/tools/adn/README.md @@ -0,0 +1,77 @@ +# 🧬 ADN Tools (Núcleo Autónomo) + +## Resumen Ejecutivo +`tools/adn` es el motor de ejecución y automatización del sistema **ADN (Artificial DNA)**. Implementa la lógica necesaria para garantizar la "Armonía Integral" de la infraestructura mediante validación proactiva, gestión de eventos y protocolos de ejecución autónomos. Actúa como el puente técnico entre las especificaciones declarativas (hebras `.md`) y la realidad operativa del servidor. + +--- + +## 🔍 Análisis Estructural + +### Arquitectura +El proyecto sigue un patrón **Modular y Dirigido por Eventos**: +- **Entry Point**: `tools/adn.rb` centraliza la interfaz de línea de comandos (CLI). +- **Bus de Eventos**: `ADN::Eventos` (Singleton) gestiona la comunicación desacoplada entre componentes. +- **Motor de Triggers**: `ADN::Triggers` (Singleton) orquestra acciones automáticas basadas en la configuración de `adn/triggers.yml`. +- **Estrategia de Subcomandos**: Cada funcionalidad mayor reside en su propio archivo `subcomando_*.rb`, facilitando la extensibilidad. + +### Archivos Críticos +1. **`adn.rb`**: El orquestador principal. Si este falla, se pierde la interfaz de gestión. +2. **`adn/eventos.rb`**: El "sistema nervioso". Permite que un cambio en una bitácora dispare validaciones o backups. +3. **`adn/validador.rb`**: El "guardián" de la calidad. Protege la integridad de las bitácoras y fichas de nodos. +4. **`adn/triggers.rb`**: Implementa la lógica de reacción del sistema. + +--- + +## ⚙️ Configuración y Tecnologías + +### Stack Tecnológico +- **Lenguaje**: Ruby (Enfoque en scripting robusto y legible). +- **Configuración**: YAML (`adn/triggers.yml` y `tools/adn/config.yml`). +- **Logging**: JSON estructurado para integración con sistemas de monitoreo externos. +- **Hooks**: Integración con Git mediante `tools/adn/hooks/pre-commit`. + +### Comandos Esenciales +```bash +# Validar cumplimiento de bitácoras +./tools/adn/run validador + +# Ejecutar backup seguro de un nodo +./tools/adn/run backup + +# Ver salud del sistema +./tools/adn/run salud --json + +# Sincronizar bitácoras con la API +./tools/adn/run sync +``` + +--- + +## 🧠 Contexto para IA (Directivas) + +### Convenciones Proyectuales +- **Singleton**: Los motores centrales (Eventos, Triggers, Logger) deben accederse siempre mediante su instancia única. +- **Bitácora-First**: Ninguna acción operativa debe ejecutarse sin antes registrarse en la bitácora del día actual. +- **Seguridad**: Nunca exponer secretos en `stdout`. Usar `ns8-candados run` para inyección de entorno. + +### Áreas Delicadas / Deuda Técnica +- ⚠️ **Sincronización Batch**: El sistema de triggers utiliza `stubs` para muchas acciones de la Fase 2 que aún deben desarrollarse. +- ⚠️ **Modo Watch**: El validador en tiempo real (basado en `listen`) es una tarea pendiente de la Fase 1. +- ⚠️ **Métricas**: La extracción de telemetría desde las bitácoras es incipiente y requiere mayor robustez en el parser. + +--- + +## 🚀 Próximos Pasos + +### Tareas Prioritarias +1. **Implementar Triggers**: Desarrollar la lógica real para las 14 acciones definidas en `adn/triggers.yml`. +2. **Refinar `backup`**: Añadir soporte para protocolos XEN y Rclone dentro del subcomando unificado. +3. **Dashboard de Salud**: Desarrollar la vista visual de `./tools/adn/run salud --dashboard`. + +### Preguntas para el Desarrollador +- ¿Cómo se deben manejar las colisiones de eventos si dos triggers disparan acciones contradictorias? +- ¿Es necesario persistir el estado del bus de eventos o es puramente efímero? +- ¿Qué nivel de criticidad tiene el fallo de un trigger en la operación general? + +--- +*Documentación generada automáticamente siguiendo el estándar `docs/prompt/documentacion.md`.* diff --git a/tools/adn/clients/dtic_bitacoras_api.rb b/tools/adn/clientes/dtic_bitacoras_api.rb similarity index 100% rename from tools/adn/clients/dtic_bitacoras_api.rb rename to tools/adn/clientes/dtic_bitacoras_api.rb diff --git a/tools/adn/comando/backup.rb b/tools/adn/comando/backup.rb new file mode 100644 index 00000000..f2d6c77a --- /dev/null +++ b/tools/adn/comando/backup.rb @@ -0,0 +1,191 @@ +# frozen_string_literal: true + +require 'optparse' +require 'fileutils' +require 'date' + +module ADN + class SubcomandoBackup + def initialize(args, logger) + @args = args + @logger = logger + @opciones = { modo: 'stop', storage: 'local', compress: 'gzip' } + end + + def ejecutar + parser = OptionParser.new do |opts| + opts.banner = "Uso: ./tools/adn/run backup [OPCIONES]" + opts.separator "" + opts.separator "Ejecuta un backup remoto siguiendo el protocolo ADN" + opts.separator "" + opts.separator "Opciones:" + opts.on("--mode MODO", "Modo de backup (stop, snapshot, suspend. Default: stop)") do |m| + @opciones[:modo] = m + end + opts.on("-h", "--help", "Mostrar esta ayuda") do + puts opts + exit + end + end + + begin + parser.parse!(@args) + rescue OptionParser::InvalidOption => e + @logger.error("Opción inválida: #{e.message}") + puts parser + exit 1 + end + + if @args.empty? + @logger.error("Se requiere nombre de nodo") + puts parser + exit 1 + end + + nodo = @args[0] + ejecutar_backup(nodo) + end + + private + + def ejecutar_backup(nodo) + @logger.info("Iniciando flujo ADN-Backup para nodo: #{nodo}") + + # 1. Obtener contexto del nodo + datos = obtener_contexto_nodo(nodo) + unless datos + @logger.error("No se pudo determinar el contexto técnico para #{nodo}") + exit 1 + end + + @logger.info("Contexto validado: VM #{datos[:vmid]} en #{datos[:host_name]} (#{datos[:host_ip]})") + + # 2. Registrar inicio en bitácora (Bitácora-First) + registrar_inicio_bitacora(nodo, datos) + + # 3. Ejecución segura vía ns8-candados + @logger.info("Solicitando autorización y ejecutando backup remoto...") + + comando_vzdump = "vzdump #{datos[:vmid]} --mode #{@opciones[:modo]} --storage #{@opciones[:storage]} --compress #{@opciones[:compress]}" + cmd_seguro = "sshpass -e ssh -o StrictHostKeyChecking=no root@#{datos[:host_ip]} \"#{comando_vzdump}\"" + + # Usamos el binario de ruby y la ruta absoluta para ns8-candados + candados_path = File.join(ADN::PROJECT_ROOT, 'tools', 'ns8-candados', 'ns8-candados.rb') + + # Ejecutar con ns8-candados run + # Nota: el comando 'authorize' debe haberse ejecutado antes o lo incluimos + puts "#{Color::DIM}🔓 Abriendo candado para sesión de backup...#{Color::RESET}" + system("ruby #{candados_path} authorize > /dev/null 2>&1") + + success = system("ruby #{candados_path} run admindasu SSHPASS '#{cmd_seguro}'") + + # 4. Finalizar y cerrar + system("ruby #{candados_path} cerrar > /dev/null 2>&1") + + if success + @logger.exito("Backup de #{nodo} completado exitosamente.") + registrar_fin_bitacora(nodo, true) + else + @logger.error("La ejecución del backup falló.") + registrar_fin_bitacora(nodo, false) + exit 1 + end + end + + def obtener_contexto_nodo(nombre_nodo) + ruta_nodo = File.join(ADN::NODOS_DIR, "#{nombre_nodo}.md") + unless File.exist?(ruta_nodo) + @logger.error("No existe el archivo de nodo: #{ruta_nodo}") + return nil + end + + contenido = File.read(ruta_nodo) + # Buscamos VMID (puede decir "VM ID: 100" o "vmid: 100") + vmid = contenido.match(/(?:VM ID|vmid):\s*(\d+)/i)&.[](1) + + # Buscamos el host (especificamente el link o nombre entre backticks) + # Formato: - **Padre/Host**: `srv-dasu` + host_name = contenido.match(/Padre\/Host\*\*:?\s*`([^`]+)`/)&.[](1) + host_name ||= contenido.match(/Host\*\*:?\s*`([^`]+)`/)&.[](1) + + unless vmid && host_name + @logger.error("Contexto incompleto en #{nombre_nodo}.md: VMID=#{vmid}, Host=#{host_name}") + return nil + end + + # Buscar IP del host + ruta_host = File.join(ADN::NODOS_DIR, "#{host_name}.md") + host_ip = nil + if File.exist?(ruta_host) + c_host = File.read(ruta_host) + # IP en tabla: | IP | `10.0.10.1` | + host_ip ||= c_host.match(/\|\s*IP[^\|]*\|\s*`([^`]+)`\s*\|/)&.[](1) + # IP en lista: - **IP**: `10.0.10.1` + host_ip ||= c_host.match(/IP\*\*:?\s*`([^`]+)`/)&.[](1) + # IP simple: IP: 10.0.10.1 + host_ip ||= c_host.match(/IP:\s*([\d\.]+)/)&.[](1) + end + + unless host_ip + @logger.error("No se encontró IP para el host #{host_name}") + return nil + end + + if host_ip && host_ip.include?('/') + host_ip = host_ip.split('/')[0] + end + + { vmid: vmid, host_name: host_name, host_ip: host_ip } + end + + def registrar_inicio_bitacora(nodo, datos) + fecha = Date.today.iso8601 + ruta = File.join(ADN::BITACORAS_DIR, "#{fecha}.md") + return unless File.exist?(ruta) + + hora = Time.now.strftime('%H:%M') + + # Si ya existe la sección del nodo, añadimos la línea. Si no, creamos la sección. + contenido = File.read(ruta) + + entrada = <<~ENTRADA + + ### #{nodo} + + #### ⏳ - BKP-ADN - Backup Autónomo (Protocolo ADN) + Ejecución de backup gestionada por `tools/adn.rb` para la VM #{datos[:vmid]} en el host #{datos[:host_name]}. + + | I | F | Descripción | E | + | :--- | :--- | :--- | :--- | + | #{hora} | | Inicio de backup remoto vía SSH (Seguro). | [R] ⏳ | + ENTRADA + + unless contenido.include?("### #{nodo}") + # Insertar antes del footer de la IA + nuevo_contenido = contenido.sub(/