Files
dtic-DIIAA/adn/tools/bkps/lib/sync_rclone.rb
T
Ricardo Monla 6ebf1c3010 🐝 F2.T1c: Atomización rclone — 1 dron por directorio de nodo
- 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
2026-04-09 21:13:50 -03:00

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