[A04.P005] DASUTEN: Pipeline refactorizado + Documentación + Informes
Refactorización del pipeline de backups DASUTEN: 1. Flujo simplificado (un solo destino): - Backup directo a X:\ (share srv-ns8) - Eliminado paso intermedio E:\BK_SQL\sysdasuten\ - Reducido de 3 ubicaciones a 2 (X: → /var/tmp/) 2. Módulo común DasuExecutor (lib/dasu_executor.rb): - Centraliza ejecución PowerShell en dasu-sql4 - Centraliza verificación de archivos en fenix - Elimina código repetido en 6 drones (-52% líneas) 3. Drones refactorizados: - dasuten_exportar_srvv-fenix.rb (FULL → X:) - dasuten_exportar_diferencial_srvv-fenix.rb (DIF → X:) - dasuten_transferir_srvv-fenix-srv-ns8.rb (simplificado) - dasuten_restaurar_dasu-sql4.rb (usa DasuExecutor) - dasuten_restaurar_diferencial_dasu-sql4.rb (usa DasuExecutor) - dasuten_download_drive-dasu-sql4.rb (usa DasuExecutor) - dasuten_verificar-integridad_dasu-sql4.rb (usa DasuExecutor) 4. Integración BKPs mantenida: - T8-T15: Tareas individuales - C7: dasuten_full (pipeline semanal) - C8: dasuten_diferencial (pipeline diario) 5. Documentación actualizada: - A04.P006_Backups-DASUTEN.md: flujo simplificado + módulo común - Lecciones aprendidas agregadas 6. Nuevos archivos: - Informes técnicos DASUTEN (docs/informes/2026-04_DASUTEN-migracion/) - A01.P011/P012: Documentación de drones y pipeline - proc_linux.rb, ops.rb: Utilitarios del ecosistema 7. Scripts legacy (para referencia): - orquestador_pipeline.rb, orquestador_pipeline_diferencial.rb - dasuten_upload_srv-ns8-drive.rb
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# ==========================================================
|
||||
# adn/tools/bkps/lib/proc_linux.rb — Procesador Linux (apt)
|
||||
#
|
||||
# Fase 2: Soporta orquestación 1-dron-por-nodo
|
||||
# listar(tarea) → lista nodos Linux pendientes
|
||||
# ejecutar_uno(tarea, nodo) → actualiza UN solo nodo
|
||||
# ejecutar(tarea) → actualiza TODOS (legacy)
|
||||
# ==========================================================
|
||||
|
||||
require 'shellwords'
|
||||
require 'net/ssh'
|
||||
|
||||
module ADN
|
||||
module BKPs
|
||||
module Procesador
|
||||
class Linux
|
||||
def initialize(log)
|
||||
@log = log
|
||||
end
|
||||
|
||||
# Scanner: devuelve lista de nodos Linux a actualizar
|
||||
# Lee los archivos .md de nodos/ y filtra los Linux (Debian/Ubuntu)
|
||||
def listar(tarea)
|
||||
nodos_dir = File.join(ADN::NODOS_DIR || 'nodos', '')
|
||||
|
||||
unless Dir.exist?(nodos_dir)
|
||||
@log.error("Directorio de nodos no existe: #{nodos_dir}")
|
||||
return []
|
||||
end
|
||||
|
||||
# Filtrar nodos Linux (excluye Windows y otros)
|
||||
nodos_linux = Dir.glob(File.join(nodos_dir, '*.md'))
|
||||
.select { |f| es_nodo_linux?(f) }
|
||||
.map { |f| extraer_info_nodo(f) }
|
||||
.compact
|
||||
.reject { |n| n[:ip].nil? || n[:ip].empty? }
|
||||
|
||||
nodos_linux.map do |nodo|
|
||||
{
|
||||
archivo: nodo[:hostname], # Identificador para ejecutar_uno
|
||||
vm: nodo[:hostname], # Nombre visible (nodo)
|
||||
ruta: nodo[:ip], # IP del nodo
|
||||
destino: nodo[:ip], # Mismo destino (es SSH)
|
||||
tamaño: 0, # No aplicable para updates
|
||||
pendiente: true, # Siempre verificar
|
||||
metadata: nodo # Info adicional (usuario, etc.)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Atómico: actualiza UN solo nodo Linux vía SSH
|
||||
def ejecutar_uno(tarea, nombre_nodo)
|
||||
ruta_md = File.join(ADN::NODOS_DIR || 'nodos', "#{nombre_nodo}.md")
|
||||
nodo_info = File.exist?(ruta_md) ? extraer_info_nodo(ruta_md) : (tarea[:metadata] || {})
|
||||
ip = nodo_info[:ip] || tarea[:origen]
|
||||
usuario = nodo_info[:usuario] || tarea[:usuario] || 'root'
|
||||
|
||||
@log.info("🐧 Actualización Linux: #{nombre_nodo}")
|
||||
@log.info(" SSH: #{usuario}@#{ip}")
|
||||
|
||||
begin
|
||||
Net::SSH.start(ip, usuario, keys_only: true, timeout: 10) do |ssh|
|
||||
# Variables de entorno para evitar interactividad
|
||||
env_vars = "DEBIAN_FRONTEND=noninteractive DEBIAN_PRIORITY=critical"
|
||||
apt_opts = "-o Dpkg::Options::='--force-confdef' -o Dpkg::Options::='--force-confold'"
|
||||
|
||||
# Paso 1: apt update
|
||||
@log.info(" [1/3] Ejecutando apt update...")
|
||||
output_update = ssh.exec!("sudo #{env_vars} apt-get update 2>&1")
|
||||
@log.info(" #{output_update.lines.last&.strip || 'OK'}")
|
||||
|
||||
# Paso 2: apt upgrade -y
|
||||
@log.info(" [2/3] Ejecutando apt upgrade -y...")
|
||||
output_upgrade = ssh.exec!("sudo #{env_vars} apt-get upgrade -y #{apt_opts} 2>&1")
|
||||
@log.info(" #{output_upgrade.lines.last&.strip || 'OK'}")
|
||||
|
||||
# Paso 3: apt autoremove -y (limpieza)
|
||||
@log.info(" [3/3] Ejecutando apt autoremove -y...")
|
||||
output_autoremove = ssh.exec!("sudo #{env_vars} apt-get autoremove -y 2>&1")
|
||||
@log.info(" #{output_autoremove.lines.last&.strip || 'OK'}")
|
||||
|
||||
@log.info("✔ Nodo #{nombre_nodo} actualizado exitosamente.")
|
||||
true
|
||||
end
|
||||
rescue => e
|
||||
@log.error("✗ Error actualizando #{nombre_nodo}: #{e.message}")
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Legacy: actualiza TODOS los nodos (compatibilidad)
|
||||
def ejecutar(tarea)
|
||||
@log.info("--- #{tarea[:texto]} ---")
|
||||
@log.info("Origen: #{tarea[:origen]}")
|
||||
|
||||
nodos = listar(tarea)
|
||||
if nodos.empty?
|
||||
@log.info("Sin nodos Linux encontrados.")
|
||||
return true
|
||||
end
|
||||
|
||||
@log.info("Encontrados #{nodos.length} nodo(s) Linux.")
|
||||
actualizados = 0
|
||||
fallidos = 0
|
||||
|
||||
nodos.each do |nodo|
|
||||
@log.info("\n" + "=" * 50)
|
||||
if ejecutar_uno(tarea, nodo[:vm])
|
||||
actualizados += 1
|
||||
else
|
||||
fallidos += 1
|
||||
end
|
||||
end
|
||||
|
||||
@log.info("\n" + "=" * 50)
|
||||
@log.info("✔ #{actualizados} nodo(s) actualizado(s), #{fallidos} fallido(s).")
|
||||
fallidos == 0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Verifica si el nodo es Linux (busca Debian, Ubuntu, Linux en el .md)
|
||||
def es_nodo_linux?(archivo_md)
|
||||
contenido = File.read(archivo_md)
|
||||
|
||||
# Excluir explícitamente Windows
|
||||
return false if contenido.match?(/Windows\s+(Server\s+)?\d+/i)
|
||||
return false if contenido.match?(/Windows\s+10/i)
|
||||
return false if contenido.match?(/Windows\s+11/i)
|
||||
|
||||
# Incluir Linux/Debian/Ubuntu
|
||||
return true if contenido.match?(/Debian/i)
|
||||
return true if contenido.match?(/Ubuntu/i)
|
||||
return true if contenido.match?(/Linux/i)
|
||||
|
||||
# Incluir si tiene IP LAN y es srvv-* o srv-* (convención de nombres)
|
||||
hostname = File.basename(archivo_md, '.md')
|
||||
return true if hostname.match?(/^srvv?-/)
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
# Extrae información del nodo desde el archivo .md
|
||||
def extraer_info_nodo(archivo_md)
|
||||
contenido = File.read(archivo_md)
|
||||
hostname = File.basename(archivo_md, '.md')
|
||||
|
||||
# Extraer IP LAN
|
||||
ip = contenido.match(/\*\*IP(?:\s+LAN)?\*\*:\s*`?(\d+\.\d+\.\d+\.\d+)`?/i)&.[](1) ||
|
||||
contenido.match(/\|\s*IP(?:\s+LAN)?[^\|]*\|\s*`?(\d+\.\d+\.\d+\.\d+)`?\s*\|/i)&.[](1)
|
||||
|
||||
# Extraer usuario SSH (si está especificado)
|
||||
usuario = contenido.match(/\*\*Usuario\s*SSH\*\*:\s*`?(\w+)`?/i)&.[](1) ||
|
||||
contenido.match(/\*\*SSH\*\*:\s*`?ssh\s+(\w+)@/i)&.[](1) ||
|
||||
'root'
|
||||
|
||||
# Extraer estado (para filtrar nodos offline)
|
||||
estado = contenido.match(/\*\*Estado\*\*:\s*(🟢|🔴|⚫|🚧|🟡)/i)&.[](1)
|
||||
|
||||
{
|
||||
hostname: hostname,
|
||||
ip: ip,
|
||||
usuario: usuario,
|
||||
estado: estado || '🟢'
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user