# frozen_string_literal: true # adn/tools/cli/proceso.rb — Subcomando para ejecución de procesos automatizados # ============================================================================== require 'optparse' require 'open3' require 'date' require_relative '../core/colores' require_relative '../core/nodos_info' require_relative '../core/ejecutor_remoto' require_relative '../db/core/bitacora_db' module ADN class SubcomandoProceso def initialize(args, logger) @args = args @logger = logger end def ejecutar if @args.empty? || @args[0] == 'help' mostrar_ayuda return end proceso = @args.shift sub_args = @args case proceso when 'nodos:migrar_backup' comando_migrar_backup(sub_args) else @logger.error("Proceso desconocido: #{proceso}") mostrar_ayuda exit 1 end end private def mostrar_ayuda puts "#{Color::CYAN}⚙️ Procesos Automatizados ADN#{Color::RESET}" puts "" puts "#{Color::YELLOW}Uso:#{Color::RESET} ./adn/tools/run proceso [argumentos]" puts "" puts "#{Color::BOLD}nodos:migrar_backup [OPC]#{Color::RESET} Migración de backups entre nodos" puts " --desde NODO Nodo origen" puts " --hacia NODO Nodo destino" puts " --ruta RUTA Ruta del archivo en el origen" puts " --ejecutar Ejecutar transferencia real" end def comando_migrar_backup(args) opts = { desde: nil, hacia: nil, ruta: nil, simular: true } OptionParser.new do |o| o.on("--desde NODO") { |v| opts[:desde] = v } o.on("--hacia NODO") { |v| opts[:hacia] = v } o.on("--ruta RUTA") { |v| opts[:ruta] = v } o.on("--ejecutar") { opts[:simular] = false } end.parse!(args) unless opts[:desde] && opts[:hacia] && opts[:ruta] puts "#{Color::RED}✗ Error: Faltan argumentos (--desde, --hacia, --ruta)#{Color::RESET}" return end # 1. Resolver información de nodos info_src = NodosInfo.extraer_metadata(opts[:desde]) info_dst = NodosInfo.extraer_metadata(opts[:hacia]) unless info_src && info_dst puts "#{Color::RED}✗ Error: No se pudo obtener información de #{opts[:desde]} o #{opts[:hacia]}#{Color::RESET}" return end # Ajuste manual para srv-ns8 (detectar si estamos corriendo en él) info_src[:ip] = 'localhost' if opts[:desde] == 'srv-ns8' puts "#{Color::CYAN}🚀 Preparando #{opts[:simular] ? 'SIMULACIÓN' : 'MIGRACIÓN'} de backup...#{Color::RESET}" puts " 📤 Origen : #{Color::BOLD}#{opts[:desde]}#{Color::RESET} (#{info_src[:ip]})" puts " 📥 Destino: #{Color::BOLD}#{opts[:hacia]}#{Color::RESET} (#{info_dst[:ip]})" # 2. Registrar en Bitácora id_evento = registrar_inicio_bitacora(opts) # 3. Ejecutar if opts[:simular] ejecutar_simulacion(id_evento, opts) else ejecutar_migracion_real(id_evento, opts, info_src, info_dst) end end def ejecutar_migracion_real(id_evento, opts, info_src, info_dst) puts "#{Color::BOLD}🚀 Iniciando transferencia real...#{Color::RESET}" desc = "PROCESO: Migración REAL de backup desde #{opts[:desde]} a #{opts[:hacia]}." # A. Verificar Origen print " ▸ Verificando origen... " size = nil if info_src[:ip] == 'localhost' size = File.exist?(opts[:ruta]) ? File.size(opts[:ruta]) : nil else # Usar EjecutorRemoto para verificar el archivo res = EjecutorRemoto.ejecutar(info_src, "powershell -NoProfile -Command \"if(Test-Path '#{opts[:ruta]}'){$s=Get-Item '#{opts[:ruta]}'; $s.Length}else{exit 1}\"") size = res[:success] ? res[:output].to_i : nil end if size mb = (size.to_f / 1024**2).round(2) puts "#{Color::GREEN}OK#{Color::RESET} (#{mb} MB)" desc += "\n- #{Time.now.strftime('%H:%M')} > Archivo localizado: #{mb} MB." else puts "#{Color::RED}FALLÓ#{Color::RESET} (No se encontró el archivo)" actualizar_bitacora(id_evento, { estado: '❌', descripcion: desc + "\n- ERROR: Archivo no encontrado." }) return end # B. Preparar Destino print " ▸ Preparando directorio en destino... " EjecutorRemoto.ejecutar(info_dst, "powershell -NoProfile -Command \"if(!(Test-Path C:\\temp)){New-Item -ItemType Directory -Path C:\\temp}\"") puts "#{Color::GREEN}OK#{Color::RESET}" # C. Pipeline de Streaming filename = File.basename(opts[:ruta].gsub('\\', '/')) dst_path = "C:\\temp\\#{filename}" puts " 🚀 Streaming: #{opts[:desde]} -> #{opts[:hacia]} [#{dst_path}]" # Definir comandos de stream adaptados if info_src[:ip] == 'localhost' src_cmd = ["cat", opts[:ruta]] else ps_src = "$s=[System.IO.File]::OpenRead('#{opts[:ruta].gsub('/', '\\\\')}'); $o=[Console]::OpenStandardOutput(); $b=New-Object byte[] 65536; while(($r=$s.Read($b,0,$b.Length)) -gt 0){ $o.Write($b,0,$r) }; $s.Close()" # Inyectar SSHPass vía EjecutorRemoto.comando_candados clave_src = (info_src[:host] == 'srv-dasu' ? 'srv-dasu:rmonla' : 'win_domain') ssh_user_src = "#{info_src[:usuario]}@#{info_src[:ip]}" src_cmd = EjecutorRemoto.comando_candados(clave_src, 'SSHPASS', ["sshpass", "-e", "ssh", "-p", info_src[:puerto_ssh].to_s, "-o", "StrictHostKeyChecking=no", ssh_user_src, "powershell -NoProfile -Command \"#{ps_src}\""]) end ps_dst = "$i=[Console]::OpenStandardInput(); $s=[System.IO.File]::Create('#{dst_path.gsub('/', '\\\\')}'); $b=New-Object byte[] 65536; while(($r=$i.Read($b,0,$b.Length)) -gt 0){$s.Write($b,0,$r)}; $s.Close()" clave_dst = (info_dst[:host] == 'srv-dasu' ? 'srv-dasu:rmonla' : 'admindasu') ssh_user_dst = "#{info_dst[:usuario]}@#{info_dst[:ip]}" dst_cmd = EjecutorRemoto.comando_candados(clave_dst, 'SSHPASS', ["sshpass", "-e", "ssh", "-p", info_dst[:puerto_ssh].to_s, "-o", "StrictHostKeyChecking=no", ssh_user_dst, "powershell -NoProfile -Command \"#{ps_dst}\""]) start_time = Time.now success = EjecutorRemoto.stream_pipeline(src_cmd, dst_cmd) end_time = Time.now if success duracion = (end_time - start_time).round(0) puts "#{Color::GREEN}✅ Transferencia exitosa en #{duracion} segundos.#{Color::RESET}" actualizar_bitacora(id_evento, { estado: '✅', fin: Time.now.strftime('%H:%M'), descripcion: desc + "\n- #{Time.now.strftime('%H:%M')} > Transferencia completada en #{duracion}s." }) else puts "#{Color::RED}✗ Falló la transferencia.#{Color::RESET}" actualizar_bitacora(id_evento, { estado: '❌', descripcion: desc + "\n- ERROR: Streaming fallido." }) end end # Auxiliares de Bitácora def registrar_inicio_bitacora(opts) BitacorasDB::BitacoraDB.with_connection do |db| nodo = db.find_nodo_by_nombre(opts[:hacia]) bit = db.find_bitacora_by_fecha(Date.today.to_s) || db.create_bitacora(fecha: Date.today.to_s) db.create_entrada( bitacora_id: bit['id'], nodo_id: nodo ? nodo['id'] : nil, inicio: Time.now.strftime('%H:%M'), estado: '⏳', descripcion: "PROCESO: Migración de backup #{opts[:desde]} -> #{opts[:hacia]}." )['id'] end end def actualizar_bitacora(id, data) BitacorasDB::BitacoraDB.with_connection { |db| db.update_entrada(id, data) } end def ejecutar_simulacion(id_evento, opts) puts " 🧪 Modo simulación activo." sleep(2) actualizar_bitacora(id_evento, { estado: '✅', fin: Time.now.strftime('%H:%M'), descripcion: "PROCESO: Simulación de migración completada." }) puts " #{Color::GREEN}OK#{Color::RESET}" end end end