[ADN] Commit final DB-First - Actualización completa de documentación, herramientas y componentes

This commit is contained in:
Ricardo Monla
2026-03-12 06:03:48 -03:00
parent 4b000aeb89
commit e554bb5d0d
37 changed files with 1479 additions and 122 deletions
+21
View File
@@ -0,0 +1,21 @@
# frozen_string_literal: true
# adn/tools/core/constants.rb — Definición centralizada de rutas y constantes del ADN
# ==============================================================================
module ADN
# El script 'run' está en adn/tools/run, por lo que la raíz es 2 niveles arriba
PROJECT_ROOT = File.expand_path('../../..', __dir__)
NODOS_DIR = File.join(PROJECT_ROOT, 'nodos')
PROYECTOS_DIR = File.join(PROJECT_ROOT, 'docs', 'proyectos')
BITACORAS_DIR = File.join(PROJECT_ROOT, 'docs', 'bitacoras')
LOGS_DIR = File.join(BITACORAS_DIR, '_hist')
# Directorios de herramientas
TOOLS_DIR = File.join(PROJECT_ROOT, 'adn', 'tools')
SEGURIDAD_DIR = File.join(TOOLS_DIR, 'seguridad')
# Bóveda
CANDADOS_PATH = File.join(SEGURIDAD_DIR, 'ns8-candados.rb')
end
+62
View File
@@ -0,0 +1,62 @@
# frozen_string_literal: true
# adn/tools/core/ejecutor_remoto.rb — Abstracción para ejecución segura via SSH y Candados
# ======================================================================================
require 'open3'
require_relative 'constants'
module ADN
class EjecutorRemoto
CANDADOS_CMD = ["ruby", ADN::CANDADOS_PATH]
def self.ejecutar(nodo_info, comando, options = {})
# Si es local, ejecutar directamente
if nodo_info[:ip] == 'localhost' || nodo_info[:ip] == '127.0.0.1'
puts " [LOCAL] > #{comando[0..50]}..." if options[:verbose]
output = `#{comando} 2>&1`
return { success: $?.success?, output: output }
end
clave = options[:clave] || (nodo_info[:host] == 'srv-dasu' ? 'srv-dasu:rmonla' : 'win_domain')
usuario = options[:usuario] || (nodo_info[:so] == :windows ? "monlaricardo@utnlaroja.intranet" : "rmonla")
# Construir comando SSH base como un array para evitar problemas de shell
ssh_cmd = [
"sshpass", "-e", "ssh",
"-p", (nodo_info[:puerto_ssh] || 22).to_s,
"-o", "StrictHostKeyChecking=no",
"#{usuario}@#{nodo_info[:ip]}",
comando
]
# Comando final encapsulado vía candados
# Usamos el modo array de candados: run <clave> <var> <cmd_array>
candados_full = CANDADOS_CMD + ["run", clave, "SSHPASS"] + ssh_cmd
puts " [SSH] #{nodo_info[:nombre]} (#{nodo_info[:ip]}) > #{comando[0..50]}..." if options[:verbose]
# Usar Open3 para capturar salida limpia
stdout, stderr, status = Open3.capture3(*candados_full)
{ success: status.success?, output: stdout + stderr }
end
# Pipeline para streaming (mejorado)
def self.stream_pipeline(src_array, dst_array)
begin
# Open3.pipeline acepta múltiples arrays de comando
statuses = Open3.pipeline(src_array, dst_array)
statuses.all?(&:success?)
rescue => e
puts "Error en pipeline: #{e.message}"
false
end
end
# Generador de comandos para candados (reutilizable)
def self.comando_candados(clave, env_var, comando_array)
CANDADOS_CMD + ["run", clave, env_var] + comando_array
end
end
end
+94
View File
@@ -0,0 +1,94 @@
# frozen_string_literal: true
# adn/tools/core/nodos_info.rb — Utilidades para extracción de metadatos de fichas de nodos
# ======================================================================================
require_relative 'constants'
module ADN
module NodosInfo
def self.extraer_metadata(nodo_nombre)
ruta = File.join(ADN::NODOS_DIR, "#{nodo_nombre}.md")
return nil unless File.exist?(ruta)
contenido = File.read(ruta, encoding: 'UTF-8')
{
nombre: nodo_nombre,
ip: detectar_ip(contenido),
host: detectar_host(contenido),
so: detectar_so(contenido),
vmid: detectar_vmid(contenido),
puerto_ssh: detectar_puerto_ssh(contenido) || 22,
usuario: detectar_usuario(contenido)
}
end
def self.detectar_usuario(contenido)
# | SSH | user@domain@ip:7022 | o user@ip:7022 o user@domain:7022
if contenido =~ /(?:SSH|Acceso)\*\*?:?\s*`?([a-zA-Z0-9_\-\.\@]+)/i
full = $1
if full.include?('@')
parts = full.split('@')
# Si la última parte es una IP, el usuario es el resto
if parts.last =~ /^\d{1,3}(\.\d{1,3}){3}/
return parts[0...-1].join('@')
else
# Probablemente sea user@domain or user@domain:port
return full.split(':')[0]
end
end
return full
end
nil
end
def self.detectar_ip(contenido)
# | IP / Ubicación | 10.0.10.10 |
# | IP | 10.0.100.11/24 |
if contenido =~ /\|\s*IP\s*(?:\/\s*Ubicación)?\s*\|\s*`?([\d\.]+)/i
return $1
end
# - **IP**: `10.0.10.200`
if contenido =~ /IP\*\*:\s*`?([\d\.]+)/i
return $1
end
nil
end
def self.detectar_host(contenido)
if contenido =~ /\*\*(?:Padre\/Host|Host Anfitrión|Anfitrión|Host)\*\*\s*[:\|]?\s*`?([a-zA-Z0-9\-]+)`?/i
return $1
end
nil
end
def self.detectar_so(contenido)
# | Sistema Operativo | ... | o - **Sistema Operativo**: ...
if contenido =~ /(?:\||\-)\s*\*\*?(?:Sistema Operativo|SO|OS)\*\*?\s*[:\|]\s*([^\|\n]+)/i
so_raw = $1.strip
return :windows if so_raw =~ /windows|server|win/i
return :linux if so_raw =~ /linux|ubuntu|debian|centos|rocky|proxmox/i
end
:linux # Default
end
def self.detectar_vmid(contenido)
if contenido =~ /(?:VM ID|vmid|VMID)\*\*?:?\s*`?(\d+)`?/i
return $1
end
nil
end
def self.detectar_puerto_ssh(contenido)
# | SSH | user@ip:7022 |
if contenido =~ /(?:SSH|Puerto|Puerto SSH)\*\*?:?\s*.*:(\d+)/i
return $1.to_i
end
# (Puerto 7022)
if contenido =~ /\(Puerto\s+(\d+)\)/i
return $1.to_i
end
nil
end
end
end