feat(P2603): sistema de bitácoras web dtic-BITACORAs - React 18 + Node.js + PostgreSQL
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# ==========================================================
|
||||
# dtic-BKPs - Automatic Backup Processor
|
||||
# ----------------------------------------------------------
|
||||
# APP: dtic-BKPs
|
||||
# FILE: fxs/proc_syncPmox.rb
|
||||
# VERSION: v5.5.1
|
||||
# AUTHOR: Ricardo MONLA (rmonla@)
|
||||
# LICENSE: MIT License
|
||||
# ==========================================================
|
||||
|
||||
require 'tmpdir'
|
||||
require 'shellwords'
|
||||
require 'open3'
|
||||
require 'fileutils'
|
||||
|
||||
# Carga las constantes, logger y helpers globales
|
||||
# require_relative '../core/globals'
|
||||
|
||||
# --- Helpers para montaje rclone ---
|
||||
def montar_rclone(origen_remote, tarea_id)
|
||||
dir_tmp = Dir.mktmpdir("bkps_#{tarea_id}_")
|
||||
log :info, "#{tarea_id}: Montando origen remoto #{origen_remote} en #{dir_tmp}"
|
||||
|
||||
# Ejecutar en background
|
||||
pid = spawn("rclone mount #{Shellwords.escape(origen_remote)} #{dir_tmp} --daemon --vfs-cache-mode writes")
|
||||
Process.detach(pid)
|
||||
|
||||
# Esperar un poco a que monte
|
||||
sleep 2
|
||||
|
||||
if Dir.exist?(dir_tmp) && !(Dir.entries(dir_tmp) - %w[. ..]).empty?
|
||||
return dir_tmp
|
||||
else
|
||||
msg = "#{tarea_id}: Error montando remote #{origen_remote}"
|
||||
log :error, msg
|
||||
puts "#{ICONO_ERROR} #{msg}"
|
||||
sleep 5
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
def desmontar_rclone(dir_tmp, tarea_id)
|
||||
return unless dir_tmp && Dir.exist?(dir_tmp)
|
||||
log :info, "#{tarea_id}: Desmontando #{dir_tmp}"
|
||||
system("fusermount -u #{dir_tmp} 2>/dev/null || umount #{dir_tmp} 2>/dev/null")
|
||||
FileUtils.remove_entry(dir_tmp) if Dir.exist?(dir_tmp)
|
||||
end
|
||||
|
||||
# --- Función Principal del Procesador Proxmox ---
|
||||
def procesar_backups_tipo_proxmox(tarea_cfg)
|
||||
system 'clear' or system 'cls'
|
||||
|
||||
# 1. --- Extraer configuración e inicializar variables ---
|
||||
origen_remote = tarea_cfg[:origen]
|
||||
dir_destino_base = tarea_cfg[:destino]
|
||||
eliminar_original = tarea_cfg[:eliminar_origen]
|
||||
sobrescribir_destino = tarea_cfg[:sobrescribir_destino]
|
||||
backups_procesados_llamada = 0
|
||||
|
||||
puts "\n#{NEGRITA}#{CIAN}--- #{tarea_cfg[:menu_texto]} ---#{RESET}"
|
||||
log :info, "#{tarea_cfg[:id]}: Iniciado. Origen remoto: #{origen_remote}, Eliminar: #{eliminar_original}, Sobrescribir: #{sobrescribir_destino}"
|
||||
|
||||
# 2. --- Montar origen remoto ---
|
||||
dir_origen = montar_rclone(origen_remote, tarea_cfg[:id])
|
||||
return unless dir_origen
|
||||
|
||||
begin
|
||||
@pv_disponible ||= !`which pv`.to_s.strip.empty?
|
||||
unless @pv_disponible
|
||||
if @pv_notificado.nil?
|
||||
msg = "#{tarea_cfg[:id]}: Comando 'pv' no encontrado. Compresión sin barra de progreso."
|
||||
log :warn, msg
|
||||
puts "#{ICONO_WARN} #{msg}"
|
||||
@pv_notificado = true
|
||||
end
|
||||
end
|
||||
|
||||
# 3. --- Buscar archivos .log ---
|
||||
archivos_log = Dir.glob(File.join(dir_origen, '*.log'))
|
||||
|
||||
if archivos_log.empty?
|
||||
msg = "#{tarea_cfg[:id]}: No se encontraron archivos .log en '#{dir_origen}'."
|
||||
log :warn, msg
|
||||
puts "#{ICONO_WARN} #{msg}"
|
||||
sleep 5
|
||||
$estadisticas_ejecucion[:tareas_completadas][tarea_cfg[:id]] = { archivos: 0, estado: "OK (Sin archivos)" }
|
||||
return
|
||||
end
|
||||
|
||||
puts "#{ICONO_INFO} Encontrados #{archivos_log.length} set(s) de backup Proxmox a procesar."
|
||||
|
||||
# 4. --- Iterar y procesar cada set ---
|
||||
archivos_log.each do |ruta_log|
|
||||
nombre_base_log = File.basename(ruta_log, '.log')
|
||||
begin
|
||||
archivos_fuente_del_backup = Dir.glob(File.join(dir_origen, "#{nombre_base_log}*"))
|
||||
|
||||
ruta_notes = archivos_fuente_del_backup.find { |f| f.end_with?('.notes') }
|
||||
nombre_vm = nil
|
||||
|
||||
if ruta_notes
|
||||
nombre_vm = File.read(ruta_notes).strip
|
||||
end
|
||||
|
||||
if nombre_vm.nil? || nombre_vm.empty?
|
||||
# Fallback 1: Extraer el nombre de la VM desde dentro del propio archivo .log
|
||||
begin
|
||||
File.foreach(ruta_log) do |line|
|
||||
if line =~ /INFO: VM Name:\s*(.+)$/
|
||||
nombre_vm = $1.strip
|
||||
log :info, "#{tarea_cfg[:id]}: Nombre '#{nombre_vm}' extraído del log '#{nombre_base_log}'."
|
||||
break
|
||||
end
|
||||
end
|
||||
rescue StandardError => e
|
||||
log :warn, "#{tarea_cfg[:id]}: Error leyendo log para extraer VM Name: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
if nombre_vm.nil? || nombre_vm.empty?
|
||||
# Fallback 2: extraemos el ID de la VM del nombre de archivo (ej. vzdump-qemu-101-...)
|
||||
regex_match = nombre_base_log.match(/vzdump-(?:qemu|lxc)-(\d+)-/)
|
||||
if regex_match
|
||||
nombre_vm = "VM-#{regex_match[1]}"
|
||||
log :info, "#{tarea_cfg[:id]}: Sin .notes ni nombre en log para '#{nombre_base_log}'. Respaldo asignado a: #{nombre_vm}"
|
||||
else
|
||||
log :warn, "#{tarea_cfg[:id]}: No se pudo determinar nombre de VM para '#{nombre_base_log}'. Saltando."
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
nombre_final_tar = "#{nombre_vm}_#{File.mtime(ruta_log).strftime('%Y%m%d_%H%M%S')}.tar.gz"
|
||||
dir_destino_vm = File.join(dir_destino_base, nombre_vm)
|
||||
FileUtils.mkdir_p(dir_destino_vm)
|
||||
ruta_salida_tar = File.join(dir_destino_vm, nombre_final_tar)
|
||||
|
||||
if File.exist?(ruta_salida_tar)
|
||||
if sobrescribir_destino
|
||||
msg = "#{tarea_cfg[:id]}: Destino '#{File.basename(ruta_salida_tar)}' existe. Se sobrescribirá."
|
||||
log :info, msg
|
||||
puts "#{ICONO_WARN} #{msg}"
|
||||
else
|
||||
msg = "#{tarea_cfg[:id]}: Destino '#{File.basename(ruta_salida_tar)}' existe. No se sobrescribe. Saltando."
|
||||
log :warn, msg
|
||||
puts "#{ICONO_WARN} #{msg}"
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
# --- Realizar compresión ---
|
||||
puts "#{CIAN}→ Comprimiendo set #{NEGRITA}#{nombre_base_log}#{RESET} (VM: #{nombre_vm}) a #{File.basename(ruta_salida_tar)}..."
|
||||
log :info, "#{tarea_cfg[:id]}: Comprimiendo Proxmox set '#{nombre_vm}' (base: #{nombre_base_log})"
|
||||
|
||||
nombres_para_tar = archivos_fuente_del_backup.map { |f| File.basename(f) }
|
||||
|
||||
if @pv_disponible
|
||||
cmd_tar = ['tar', '-cf', '-', '-C', dir_origen] + nombres_para_tar
|
||||
cmd_pv = ['pv', '-s', archivos_fuente_del_backup.sum { |f| File.size(f) rescue 0 }.to_s]
|
||||
cmd_gzip = ['gzip']
|
||||
|
||||
File.open(ruta_salida_tar, 'wb') do |f_out|
|
||||
Open3.pipeline(cmd_tar, cmd_pv, cmd_gzip, out: f_out).each_with_index do |status, i|
|
||||
unless status.success?
|
||||
cmd_name = ['tar', 'pv', 'gzip'][i]
|
||||
raise "Error en compresión (falló #{cmd_name} con código: #{status.exitstatus})"
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
archivos_escapados = nombres_para_tar.map { |f| Shellwords.escape(f) }.join(' ')
|
||||
cmd_tar_directo = "tar -czf #{Shellwords.escape(ruta_salida_tar)} -C #{Shellwords.escape(dir_origen)} #{archivos_escapados}"
|
||||
system(cmd_tar_directo)
|
||||
raise "Error en compresión (falló tar con código: #{$?.exitstatus})" unless $?.success?
|
||||
end
|
||||
|
||||
# --- Finalizar ---
|
||||
log :info, "#{tarea_cfg[:id]}: Compresión OK → #{ruta_salida_tar}"
|
||||
puts "\n#{ICONO_OK} Compresión OK: #{File.basename(ruta_salida_tar)}"
|
||||
|
||||
if eliminar_original
|
||||
log :info, "#{tarea_cfg[:id]}: Eliminando fuentes Proxmox '#{nombre_base_log}'..."
|
||||
archivos_fuente_del_backup.each { |f| FileUtils.rm_f(f) }
|
||||
end
|
||||
|
||||
backups_procesados_llamada += 1
|
||||
|
||||
rescue StandardError => e
|
||||
err_msg = "#{tarea_cfg[:id]}: Error procesando set '#{nombre_base_log}': #{e.message}"
|
||||
log :error, err_msg
|
||||
puts "\n#{ICONO_ERROR} #{err_msg}"
|
||||
sleep 5
|
||||
FileUtils.rm_f(ruta_salida_tar) if ruta_salida_tar
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
# 5. --- Reportar estadísticas finales ---
|
||||
$estadisticas_ejecucion[:tareas_completadas][tarea_cfg[:id]] = { archivos: backups_procesados_llamada, estado: "OK" }
|
||||
log :info, "#{tarea_cfg[:id]}: #{backups_procesados_llamada} sets Proxmox procesados."
|
||||
puts "#{ICONO_OK} Tarea '#{tarea_cfg[:menu_texto]}' completada. Procesados: #{backups_procesados_llamada}."
|
||||
sleep 1.5
|
||||
|
||||
ensure
|
||||
# Siempre desmontar aunque haya error
|
||||
desmontar_rclone(dir_origen, tarea_cfg[:id])
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user