[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:
Ricardo Monla
2026-04-14 20:06:06 -03:00
parent bc1f42db1f
commit 892019fa22
59 changed files with 5438 additions and 42 deletions
+172
View File
@@ -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