- sync_rclone.rb: listar() + ejecutar_uno() implementados - Soporta origen local (Dir.children) y remoto (rclone lsf) - Orquestador: nota dinámica (Sync vs Comprimir según tipo) - Validado dry-run: T4 (2 dirs), T5 (17 dirs), T6 (2 dirs) - Legacy ejecutar() preservado para compatibilidad
158 lines
5.1 KiB
Ruby
158 lines
5.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# ==========================================================
|
|
# adn/tools/bkps/lib/sync_rclone.rb — Sincronización rclone
|
|
# Migrado desde dtic-BKPs v6.0 → ADN::BKPs
|
|
#
|
|
# Fase 2: Soporta orquestación 1-dron-por-directorio
|
|
# listar(tarea) → lista subdirectorios (nodos)
|
|
# ejecutar_uno(tarea, nodo) → sincroniza UN solo directorio
|
|
# ejecutar(tarea) → sincroniza TODO el origen (legacy)
|
|
# ==========================================================
|
|
|
|
require 'shellwords'
|
|
|
|
module ADN
|
|
module BKPs
|
|
module Procesador
|
|
class Rclone
|
|
RCLONE_OPTS = %w[
|
|
--delete-before
|
|
--retries 5
|
|
--retries-sleep 10s
|
|
--timeout 30m
|
|
--contimeout 10m
|
|
--tpslimit 10
|
|
--progress
|
|
-v
|
|
].join(' ').freeze
|
|
|
|
def initialize(log)
|
|
@log = log
|
|
end
|
|
|
|
# Scanner: devuelve lista de subdirectorios (nodos) en el origen
|
|
# Compatible con el orquestador que espera {archivo:, vm:, tamaño:, pendiente:}
|
|
def listar(tarea)
|
|
origen = tarea[:origen]
|
|
|
|
# Determinar si el origen es local o remoto (rclone remote)
|
|
if origen.include?(':')
|
|
listar_remoto(tarea)
|
|
else
|
|
listar_local(tarea)
|
|
end
|
|
end
|
|
|
|
# Atómico: sincroniza UN solo directorio de nodo
|
|
def ejecutar_uno(tarea, nombre_directorio)
|
|
origen = File.join(tarea[:origen], nombre_directorio, '')
|
|
destino = File.join(tarea[:destino], nombre_directorio, '')
|
|
|
|
@log.info("☁️ Sync atómico: #{nombre_directorio}")
|
|
@log.info(" Origen: #{origen}")
|
|
@log.info(" Destino: #{destino}")
|
|
|
|
sincronizar(tarea, origen, destino)
|
|
end
|
|
|
|
# Legacy: sincroniza TODO el origen (compatibilidad con sistema previo)
|
|
def ejecutar(tarea)
|
|
@log.info("--- #{tarea[:texto]} ---")
|
|
origen = tarea[:origen]
|
|
destino = tarea[:destino]
|
|
|
|
@log.info("Origen: #{origen}")
|
|
@log.info("Destino: #{destino}")
|
|
|
|
sincronizar(tarea, origen, destino)
|
|
end
|
|
|
|
private
|
|
|
|
# ─── Listado local ──────────────────────────────────────────
|
|
def listar_local(tarea)
|
|
origen = tarea[:origen]
|
|
destino = tarea[:destino]
|
|
|
|
unless Dir.exist?(origen)
|
|
@log.error("Directorio origen no existe: #{origen}")
|
|
return []
|
|
end
|
|
|
|
Dir.children(origen)
|
|
.select { |d| File.directory?(File.join(origen, d)) }
|
|
.sort
|
|
.map do |nombre|
|
|
ruta_local = File.join(origen, nombre)
|
|
tamaño = calcular_tamaño_dir(ruta_local)
|
|
|
|
{
|
|
archivo: nombre, # Identificador para ejecutar_uno
|
|
vm: nombre, # Nombre visible (nodo)
|
|
ruta: ruta_local,
|
|
destino: File.join(destino, nombre, ''),
|
|
tamaño: tamaño,
|
|
pendiente: true # Siempre sincronizar (rclone decide deltas)
|
|
}
|
|
end
|
|
end
|
|
|
|
# ─── Listado remoto (rclone lsf) ────────────────────────────
|
|
def listar_remoto(tarea)
|
|
origen = tarea[:origen]
|
|
destino = tarea[:destino]
|
|
|
|
cmd = "rclone lsf #{Shellwords.escape(origen)} --dirs-only 2>/dev/null"
|
|
output = `#{cmd}`
|
|
|
|
unless $?.success?
|
|
@log.error("No se pudo listar remoto: #{origen}")
|
|
return []
|
|
end
|
|
|
|
output.split("\n")
|
|
.map { |d| d.chomp('/').strip }
|
|
.reject(&:empty?)
|
|
.sort
|
|
.map do |nombre|
|
|
{
|
|
archivo: nombre,
|
|
vm: nombre,
|
|
ruta: File.join(origen, nombre, ''),
|
|
destino: File.join(destino, nombre, ''),
|
|
tamaño: 0, # No calculamos tamaño remoto (costoso)
|
|
pendiente: true
|
|
}
|
|
end
|
|
end
|
|
|
|
# ─── Sincronización ─────────────────────────────────────────
|
|
def sincronizar(_tarea, origen, destino)
|
|
cmd = "rclone sync #{Shellwords.escape(origen)} #{Shellwords.escape(destino)} #{RCLONE_OPTS}"
|
|
@log.info("Ejecutando → #{cmd}")
|
|
|
|
exito = system(cmd)
|
|
codigo = $?.exitstatus
|
|
|
|
if exito
|
|
@log.info("✔ Sincronización rclone completada.")
|
|
else
|
|
@log.error("Fallo en sincronización (código: #{codigo}).")
|
|
end
|
|
|
|
exito
|
|
end
|
|
|
|
# ─── Helpers ────────────────────────────────────────────────
|
|
def calcular_tamaño_dir(ruta)
|
|
output = `du -sb #{Shellwords.escape(ruta)} 2>/dev/null`.strip
|
|
output.split("\t").first.to_i
|
|
rescue
|
|
0
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|