Files
dtic-DIIAA/tools/dtic-BKPs/dtic-BKPs_app.rb
T

294 lines
10 KiB
Ruby
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/ruby
# frozen_string_literal: true
# ==========================================================
# dtic-BKPs - Automatic Backup Processor (Unified Script)
# ----------------------------------------------------------
# APP: dtic-BKPs
# VERSION: v5.5.1
# AUTHOR: Ricardo MONLA ( rmonla@ )
# LICENSE: MIT License
# ==========================================================
# --- REQUIRES GLOBALES ---
require 'fileutils'
require 'open3'
require 'time'
require 'io/console'
require 'shellwords'
require 'logger'
# --- CARGA INICIAL DE MÓDULOS ---
# (Se asume que los otros archivos están en el mismo directorio o accesibles)
# require_relative 'core/globals'
require_relative 'fxs/config_handler'
require_relative 'fxs/ui_handlers'
require_relative 'fxs/proc_syncPmox'
require_relative 'fxs/sync_rclone'
require_relative 'fxs/proc_syncXEN'
# ==========================================================
# CONSTANTES GLOBALES
# ==========================================================
# --- CONSTANTES GLOBALES DEL SCRIPT ---
APP_NOM_BASE = 'dtic-BKPs'
APP_NOM = "#{APP_NOM_BASE}_app.rb"
APP_VER = 'v5.5.1' # Actualizado
APP_AUTOR = "Ricardo MONLA ( rmonla@ )"
APP_DIR_SCRIPT = File.dirname(File.expand_path(__FILE__))
APP_CFG_NOM_ARCHIVO = "#{APP_NOM_BASE}_tasks.rb"
APP_LOG_NOM_ARCHIVO = "logs/#{APP_NOM_BASE}.log"
APP_CFG_RUTA_ARCHIVO = File.join(APP_DIR_SCRIPT, APP_CFG_NOM_ARCHIVO)
APP_LOG_RUTA_ARCHIVO = File.join(APP_DIR_SCRIPT, APP_LOG_NOM_ARCHIVO)
# --- COLORES E ICONOS GLOBALES ---
RESET = "\e[0m"; NEGRITA = "\e[1m"; SUBRAYADO="\e[4m"; INTERMITENTE = "\e[5m"
CIAN = "\e[96m"; AMARILLO = "\e[93m"; ROJO = "\e[91m"; VERDE = "\e[92m"
ICONO_OK = "#{VERDE}#{RESET}"; ICONO_ERROR = "#{ROJO}#{RESET}"; ICONO_WARN = "#{AMARILLO}#{RESET}"; ICONO_INFO = "#{CIAN}#{RESET}"
# --- CONFIGURACIÓN GLOBAL ---
# Bloque para configurar el logger directamente aquí
require 'logger' # Asegúrate de que Logger esté cargado
$logger = Logger.new(APP_LOG_RUTA_ARCHIVO, 'daily')
$logger.level = Logger::INFO # Puedes ajustar el nivel de log aquí
$logger.formatter = proc do |severity, datetime, progname, msg|
"[#{datetime.strftime('%Y-%m-%d %H:%M:%S')}] [#{severity}] #{msg}\n"
end
$estadisticas_ejecucion = { tareas_completadas: {} }
# --- FUNCIÓN DE LOGGING GLOBAL ---
def log(nivel, mensaje)
# Usa esta expresión regular más robusta para eliminar códigos ANSI
msg_l = mensaje.gsub(/\e\[(?:\d{1,3}(?:;\d{1,3})*)?[m|K]/, '')
case nivel
when :info then $logger.info(msg_l)
when :error then $logger.error(msg_l)
when :warn then $logger.warn(msg_l)
else $logger.debug(msg_l)
end
end
# Función auxiliar para verificar si un comando existe
def which(cmd)
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
exts.each { |ext|
exe = File.join(path, "#{cmd}#{ext}")
return exe if File.executable?(exe) && !File.directory?(exe)
}
end
nil
end
# ==========================================================
# (Bucle Principal)
# ==========================================================
# Mapea los tipos de proceso a sus funciones correspondientes.
PROCESSOR_MAP = {
xva: ->(task_cfg) { procesar_backups_xva(task_cfg) },
rclone_sync: ->(task_cfg) { sincronizar_rclone(task_cfg) },
proxmox_log_notes: ->(task_cfg) { procesar_backups_tipo_proxmox(task_cfg) }
}
def ejecutar_tarea_individual(tarea_cfg)
processor = PROCESSOR_MAP[tarea_cfg[:tipo_proceso]]
if processor
processor.call(tarea_cfg)
else
puts "#{ICONO_ERROR} Tipo de proceso desconocido: #{tarea_cfg[:tipo_proceso]}"
log :error, "Tipo de proceso desconocido para tarea #{tarea_cfg[:id]}: #{tarea_cfg[:tipo_proceso]}"
end
end
def ejecutar_comando(comando_cfg, todas_las_tareas)
system 'clear' or system 'cls'
puts "\n#{NEGRITA}#{CIAN}--- Ejecutando Comando: #{comando_cfg[:menu_texto]} ---#{RESET}"
log :info, "Iniciando ejecución del comando: #{comando_cfg[:id]}"
comando_cfg[:tareas].each_with_index do |id_tarea, index|
tarea_actual = todas_las_tareas.find { |t| t[:id] == id_tarea }
if tarea_actual
puts "\n#{AMARILLO}--- Paso #{index + 1}/#{comando_cfg[:tareas].length}: #{tarea_actual[:menu_texto]} ---#{RESET}"
log :info, "Ejecutando tarea '#{id_tarea}' dentro del comando '#{comando_cfg[:id]}'."
ejecutar_tarea_individual(tarea_actual)
# Aquí puedes añadir lógica para pausar o continuar automáticamente
# if !AUTO_AVANZAR_TAREA_MENU # O una configuración específica para comandos
# puts "\nPresiona Enter para continuar con la siguiente tarea del comando..."
# $stdin.gets
# end
else
puts "#{ICONO_ERROR} Tarea con ID '#{id_tarea}' no encontrada para el comando '#{comando_cfg[:id]}'. Saltando."
log :error, "Tarea con ID '#{id_tarea}' no encontrada para el comando '#{comando_cfg[:id]}'."
end
end
puts "\n#{ICONO_OK} Comando '#{comando_cfg[:menu_texto]}' completado."
log :info, "Comando '#{comando_cfg[:id]}' completado."
sleep 2
end
def bucle_principal(tareas_config, comandos_config, auto_avanzar)
loop do
opciones_menu = []
# Añadir tareas individuales al menú
tareas_config.each_with_index do |tarea_cfg, index|
opciones_menu << {
id: "T#{index + 1}",
texto: tarea_cfg[:menu_texto],
tipo: :tarea,
config: tarea_cfg
}
end
# Añadir comandos al menú
comandos_config.each_with_index do |comando_cfg, index|
opciones_menu << {
id: "C#{index + 1}",
texto: comando_cfg[:menu_texto],
tipo: :comando,
config: comando_cfg
}
end
opciones_menu << { id: 'E', texto: "Editar tareas y comandos programados", tipo: :especial }
opciones_menu << { id: 'S', texto: "Salir del procesador", tipo: :especial }
system 'clear' or system 'cls'
mostrar_encabezado_app
puts "\n#{CIAN}--- Menú Principal ---#{RESET}"
puts "\n#{NEGRITA}Tareas Individuales: #{RESET}"
opciones_menu.select { |op| op[:tipo] == :tarea }.each do |op|
puts "#{AMARILLO}#{op[:id]}.#{RESET} #{op[:texto]}"
end
puts "\n#{NEGRITA}Comandos (Secuencias de Tareas): #{RESET}"
opciones_menu.select { |op| op[:tipo] == :comando }.each do |op|
puts "#{AMARILLO}#{op[:id]}.#{RESET} #{op[:texto]}"
end
puts "\n#{CIAN}--------------------#{RESET}"
# Opciones especiales al final
opciones_menu.select { |op| op[:tipo] == :especial }.each do |op|
puts "#{AMARILLO}#{op[:id]}.#{RESET} #{op[:texto]}"
end
puts "#{CIAN}--------------------#{RESET}"
opcion_usuario = leer_entrada_simple("Selecciona una opción > ").strip.upcase
opcion_encontrada = opciones_menu.find { |op| op[:id].to_s == opcion_usuario }
if opcion_encontrada
case opcion_encontrada[:tipo]
when :tarea
ejecutar_tarea_individual(opcion_encontrada[:config])
when :comando
ejecutar_comando(opcion_encontrada[:config], tareas_config)
when :especial
case opcion_encontrada[:id]
when 'E'
modificar_tareas_y_comandos_menu(tareas_config, comandos_config, auto_avanzar)
# Recargar configuración después de editar
tareas_config, comandos_config, auto_avanzar = cargar_configuracion
when 'S'
puts "Saliendo..."; exit 0
end
end
# Pausar si no es una opción de edición/salir
unless ['E', 'S'].include?(opcion_encontrada[:id].to_s) || auto_avanzar
puts "\nPresiona Enter para continuar..."
$stdin.gets
end
else
puts "#{ICONO_ERROR} Opción no válida."
sleep 1
end
end
end
# --- Inicio del Script ---
Signal.trap("INT") { puts "\n#{ROJO}Ctrl+C detectado. Saliendo...#{RESET}"; log :warn, "Script interrumpido (Ctrl+C)."; exit 130; }
# === AUTO-SCREEN WRAPPER ===
# Verificamos si ya estamos dentro de una sesión de GNU Screen
unless ENV['TERM'] == 'screen' || ENV['TERM'] =~ /^screen/ || ENV['STY']
puts "\n#{CIAN} Relanzando dtic-BKPs en una sesión separada (screen) para permitir desatención...#{RESET}"
# Construimos el comando original con sus argumentos
comando_original = [$PROGRAM_NAME] + ARGV
comando_escapado = comando_original.map { |arg| Shellwords.escape(arg) }.join(' ')
# Nombre de sesión dinámico
nombre_sesion = "dticBKPs_#{Time.now.strftime('%H%M%S')}"
puts "Para reconectarte luego, usa: #{AMARILLO}screen -r #{nombre_sesion}#{RESET}"
sleep 2
# Reemplaza el proceso actual por screen ejecutando este mismo script
exec('screen', '-S', nombre_sesion, 'bash', '-c', "#{comando_escapado}; echo ''; read -p 'Presiona Enter para cerrar la sesión...'")
end
log :info, "===== Inicio script #{APP_NOM} #{APP_VER} (en Screen) ====="
tareas_config, comandos_config, auto_avanzar = cargar_configuracion
opciones_menu = []
# Añadir tareas individuales al menú
tareas_config.each_with_index do |tarea_cfg, index|
opciones_menu << {
id: "T#{index + 1}",
texto: tarea_cfg[:menu_texto],
tipo: :tarea,
config: tarea_cfg
}
end
# Añadir comandos al menú
comandos_config.each_with_index do |comando_cfg, index|
opciones_menu << {
id: "C#{index + 1}",
texto: comando_cfg[:menu_texto],
tipo: :comando,
config: comando_cfg
}
end
opciones_menu << { id: 'E', texto: "Editar tareas y comandos programados", tipo: :especial }
opciones_menu << { id: 'S', texto: "Salir del procesador", tipo: :especial }
if ARGV[0]
opcion_usuario = ARGV[0].strip.upcase
opcion_encontrada = opciones_menu.find { |op| op[:id].to_s == opcion_usuario }
if opcion_encontrada
case opcion_encontrada[:tipo]
when :tarea
ejecutar_tarea_individual(opcion_encontrada[:config])
when :comando
ejecutar_comando(opcion_encontrada[:config], tareas_config)
when :especial
case opcion_encontrada[:id]
when 'E'
modificar_tareas_y_comandos_menu(tareas_config, comandos_config, auto_avanzar)
# Recargar configuración después de editar
tareas_config, comandos_config, auto_avanzar = cargar_configuracion
when 'S'
puts "Saliendo..."; exit 0
end
end
log :info, "Ejecutado automáticamente desde línea de comandos: #{opcion_usuario}"
exit 0
else
puts "#{ICONO_ERROR} Opción no válida: #{opcion_usuario}"
log :error, "Opción no válida desde línea de comandos: #{opcion_usuario}"
exit 1
end
else
bucle_principal(tareas_config, comandos_config, auto_avanzar)
end