#!/usr/bin/env ruby # [PROC-01] - Mantenimiento de Backups Proxmox (Ruby Edition) # Lógica: Granular por VMID, Retención 6 días, Destino zfsDISCO1 require 'date' require 'fileutils' STORAGE_NAME = "zfsDISCO1" # NOTA: En Proxmox, la ruta suele ser /mnt/pve/zfsDISCO1 o similar. # Ajustamos a la ruta montada del storage. BASE_PATH = "/mnt/pve/#{STORAGE_NAME}/dump" RETENTION_DAYS = 6 VM_PATTERN = /^srv[v]?-/ def log(message, type = "INFO") timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S") puts "[#{timestamp}] [#{type}] #{message}" end def get_vmid_list # Obtener IDs de VMs (QEMU) y Contenedores (LXC) ids = [] # QEMU VMs `qm list`.each_line.with_index do |line, idx| next if idx == 0 # Cabecera parts = line.split vmid = parts[0] name = parts[1] ids << vmid if name =~ VM_PATTERN end # LXC Containers `pct list`.each_line.with_index do |line, idx| next if idx == 0 parts = line.split vmid = parts[0] name = parts[2] # El nombre suele ser la 3er columna en pct list ids << vmid if name =~ VM_PATTERN end ids.uniq end def prune_old_backups(vmid) log("Iniciando fase de limpieza para VMID: #{vmid}...") return unless Dir.exist?(BASE_PATH) # Patrón de archivos de Proxmox: vzdump-[qemu|lxc]--.* # Buscamos archivos de este VMID específico files = Dir.glob("#{BASE_PATH}/vzdump-*-#{vmid}-*") files.each do |file| mtime = File.mtime(file) age_days = (Date.today - mtime.to_date).to_i if age_days > RETENTION_DAYS log("Eliminando backup antiguo: #{File.basename(file)} (Antigüedad: #{age_days} días)", "WARN") begin FileUtils.rm(file) rescue => e log("Error al eliminar #{file}: #{e.message}", "ERROR") end end end end def run_backup(vmid) log("Iniciando backup para VMID: #{vmid} hacia #{STORAGE_NAME}...") # Usamos --mode stop para asegurar reinicio automático y consistencia cmd = "vzdump #{vmid} --storage #{STORAGE_NAME} --mode stop --compress zstd" log("Ejecutando: #{cmd}") success = system(cmd) if success log("Backup completado exitosamente para VMID: #{vmid}", "SUCCESS") else log("FALLO en el backup para VMID: #{vmid}", "ERROR") end end # --- EJECUCIÓN PRINCIPAL --- log("=== INICIO DE CICLO DE MANTENIMIENTO DE BACKUPS ===") unless Dir.exist?(BASE_PATH) log("Error: No se encuentra la ruta del storage #{BASE_PATH}. ¿Está montado?", "ERROR") exit 1 end vmids = get_vmid_list log("Nodos detectados para procesar: #{vmids.join(', ')}") vmids.each do |vmid| prune_old_backups(vmid) run_backup(vmid) puts "-" * 40 end log("=== FIN DE CICLO DE MANTENIMIENTO ===")