docs: armonización masiva de bitácoras, categorización de nodos y diagnóstico srv-dasu
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# ==========================================================
|
||||
# dtic-BKPs/lib/proc_proxmox.rb — Procesador de backups Proxmox
|
||||
# ==========================================================
|
||||
|
||||
require 'tmpdir'
|
||||
require 'shellwords'
|
||||
require 'open3'
|
||||
require 'fileutils'
|
||||
|
||||
module DTICBKPs
|
||||
module Procesador
|
||||
class Proxmox
|
||||
def initialize(log)
|
||||
@log = log
|
||||
@pv = system('which pv > /dev/null 2>&1')
|
||||
end
|
||||
|
||||
def ejecutar(tarea)
|
||||
@log.titulo(tarea[:texto])
|
||||
@log.info("#{tarea[:id]}: Origen remoto: #{tarea[:origen]}")
|
||||
|
||||
dir_tmp = montar(tarea[:origen], tarea[:id])
|
||||
return false unless dir_tmp
|
||||
|
||||
procesados = 0
|
||||
|
||||
begin
|
||||
logs = Dir.glob(File.join(dir_tmp, '*.log'))
|
||||
if logs.empty?
|
||||
@log.warn("#{tarea[:id]}: Sin archivos .log en origen.")
|
||||
return true
|
||||
end
|
||||
|
||||
@log.info("Encontrados #{logs.length} set(s) de backup Proxmox.")
|
||||
|
||||
logs.each do |ruta_log|
|
||||
base = File.basename(ruta_log, '.log')
|
||||
begin
|
||||
fuentes = Dir.glob(File.join(dir_tmp, "#{base}*"))
|
||||
nombre_vm = extraer_nombre_vm(fuentes, ruta_log, base, tarea[:id])
|
||||
next unless nombre_vm
|
||||
|
||||
nombre_tar = "#{nombre_vm}_#{File.mtime(ruta_log).strftime('%Y%m%d_%H%M%S')}.tar.gz"
|
||||
dir_dest = File.join(tarea[:destino], nombre_vm)
|
||||
FileUtils.mkdir_p(dir_dest)
|
||||
ruta_tar = File.join(dir_dest, nombre_tar)
|
||||
|
||||
if File.exist?(ruta_tar)
|
||||
if tarea[:sobrescribir]
|
||||
@log.warn("#{File.basename(ruta_tar)} existe. Sobrescribiendo.")
|
||||
else
|
||||
@log.warn("#{File.basename(ruta_tar)} existe. Saltando.")
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
@log.paso("Comprimiendo #{base} (VM: #{nombre_vm})")
|
||||
comprimir(dir_tmp, fuentes, ruta_tar)
|
||||
@log.ok("Compresión OK: #{File.basename(ruta_tar)}")
|
||||
|
||||
if tarea[:eliminar_origen]
|
||||
fuentes.each { |f| FileUtils.rm_f(f) }
|
||||
@log.info("Fuentes eliminadas de origen.")
|
||||
end
|
||||
|
||||
procesados += 1
|
||||
rescue => e
|
||||
@log.error("#{tarea[:id]}: Error procesando '#{base}': #{e.message}")
|
||||
FileUtils.rm_f(ruta_tar) if ruta_tar && File.exist?(ruta_tar)
|
||||
end
|
||||
end
|
||||
|
||||
@log.ok("#{procesados} sets Proxmox procesados.")
|
||||
true
|
||||
ensure
|
||||
desmontar(dir_tmp, tarea[:id])
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def extraer_nombre_vm(fuentes, ruta_log, base, tarea_id)
|
||||
# 1. Intentar .notes
|
||||
notes = fuentes.find { |f| f.end_with?('.notes') }
|
||||
nombre = notes ? File.read(notes).strip : nil
|
||||
|
||||
# 2. Intentar desde el .log (INFO: VM Name: ...)
|
||||
if nombre.nil? || nombre.empty?
|
||||
File.foreach(ruta_log) do |line|
|
||||
if line =~ /INFO: VM Name:\s*(.+)$/
|
||||
nombre = $1.strip
|
||||
break
|
||||
end
|
||||
end rescue nil
|
||||
end
|
||||
|
||||
# 3. Fallback: ID de VM del nombre de archivo
|
||||
if nombre.nil? || nombre.empty?
|
||||
match = base.match(/vzdump-(?:qemu|lxc)-(\d+)-/)
|
||||
nombre = match ? "VM-#{match[1]}" : nil
|
||||
end
|
||||
|
||||
@log.warn("#{tarea_id}: Sin nombre de VM para '#{base}'. Saltando.") unless nombre
|
||||
nombre
|
||||
end
|
||||
|
||||
def comprimir(dir_origen, fuentes, ruta_salida)
|
||||
nombres = fuentes.map { |f| File.basename(f) }
|
||||
if @pv
|
||||
cmd_tar = ['tar', '-cf', '-', '-C', dir_origen] + nombres
|
||||
cmd_pv = ['pv', '-s', fuentes.sum { |f| File.size(f) rescue 0 }.to_s]
|
||||
cmd_gz = ['gzip']
|
||||
File.open(ruta_salida, 'wb') do |f|
|
||||
Open3.pipeline(cmd_tar, cmd_pv, cmd_gz, out: f).each_with_index do |s, i|
|
||||
raise "Falló #{%w[tar pv gzip][i]} (#{s.exitstatus})" unless s.success?
|
||||
end
|
||||
end
|
||||
else
|
||||
args = nombres.map { |f| Shellwords.escape(f) }.join(' ')
|
||||
cmd = "tar -czf #{Shellwords.escape(ruta_salida)} -C #{Shellwords.escape(dir_origen)} #{args}"
|
||||
system(cmd) or raise "tar falló (#{$?.exitstatus})"
|
||||
end
|
||||
end
|
||||
|
||||
def montar(remoto, tarea_id)
|
||||
dir = Dir.mktmpdir("bkps_#{tarea_id}_")
|
||||
@log.info("Montando #{remoto} en #{dir}")
|
||||
pid = spawn("rclone mount #{Shellwords.escape(remoto)} #{dir} --daemon --vfs-cache-mode writes")
|
||||
Process.detach(pid)
|
||||
sleep 2
|
||||
|
||||
if Dir.exist?(dir) && !(Dir.entries(dir) - %w[. ..]).empty?
|
||||
dir
|
||||
else
|
||||
@log.error("Error montando #{remoto}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def desmontar(dir, tarea_id)
|
||||
return unless dir && Dir.exist?(dir)
|
||||
@log.info("Desmontando #{dir}")
|
||||
system("fusermount -u #{dir} 2>/dev/null || umount #{dir} 2>/dev/null")
|
||||
FileUtils.remove_entry(dir) rescue nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user