[A04.P006] Exportar backup en doble ubicación (fenix + share ns8)

Cambios realizados:

1. dasuten_exportar_srvv-fenix.rb:
   - Genera backup en disco local: E:\BK_SQL\sysdasuten\
   - Copia automáticamente a unidad de red X: (share srv-ns8)
   - Usa PowerShell para copiar al share mapeado
   - Verifica ambos destinos

2. dasuten_transferir_srvv-fenix-srv-ns8.rb:
   - Simplificado: ya no descarga desde fenix vía SMB
   - El backup ya está en /mnt/ns8Disco2/bkps-dasuten/
   - Copia local: share → /var/tmp/ (para rclone upload)

Flujo actualizado:
  1. SQL Server → E:\BK_SQL\sysdasuten\archivo.bak (local fenix)
  2. PowerShell → X:\archivo.bak (share srv-ns8)
  3. Transfer dron → /var/tmp/archivo.bak (para rclone)
  4. Upload dron → Google Drive

Requisito en srvv-fenix:
  net use X: \\10.0.10.8\bkps-dasuten /user:rmonla <password>

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ricardo Monla
2026-04-14 19:44:13 -03:00
co-authored by Claude Opus 4.6
parent c6970f7d7a
commit 900457a778
2 changed files with 96 additions and 85 deletions
@@ -15,13 +15,21 @@ require 'json'
# Nombre del archivo con timestamp # Nombre del archivo con timestamp
TIMESTAMP = Time.now.strftime('%Y%m%d_%H%M%S') TIMESTAMP = Time.now.strftime('%Y%m%d_%H%M%S')
BACKUP_DIR = "E:\\BK_SQL\\sysdasuten" BACKUP_FILE = "sysdasuten_FULL_#{TIMESTAMP}.bak"
BACKUP_FILE = "#{BACKUP_DIR}\\sysdasuten_FULL_#{TIMESTAMP}.bak"
# Directorio local en fenix
BACKUP_DIR_FENIX = "E:\\BK_SQL\\sysdasuten"
BACKUP_LOCAL = "#{BACKUP_DIR_FENIX}\\#{BACKUP_FILE}"
# Unidad de red mapeada en srvv-fenix (share de srv-ns8)
BACKUP_DIR_NS8 = "X:"
BACKUP_NS8 = "#{BACKUP_DIR_NS8}\\#{BACKUP_FILE}"
puts "[*] Dron: Exportar backup COMPLETO DASUTEN" puts "[*] Dron: Exportar backup COMPLETO DASUTEN"
puts " Nodo: srvv-fenix" puts " Nodo: srvv-fenix"
puts " Destino: #{BACKUP_FILE}" puts " Destino local: #{BACKUP_LOCAL}"
puts " Tipo: FULL (completo)" puts " Destino share (X:): #{BACKUP_NS8}"
puts " Tipo: FULL (completo, con copia en doble ubicación)"
begin begin
# Autorizar candados primero (necesario para acceder a la bóveda) # Autorizar candados primero (necesario para acceder a la bóveda)
@@ -47,43 +55,74 @@ begin
) )
puts "[+] Conectado a SQL Server en #{sql_host} (auth: Windows)" puts "[+] Conectado a SQL Server en #{sql_host} (auth: Windows)"
# Crear directorio si no existe # Crear directorio local si no existe
puts "\n[0/3] Verificando directorio..." puts "\n[0/4] Verificando directorios..."
mkdir_sql = "EXEC master.sys.xp_create_subdir '#{BACKUP_DIR}'" mkdir_sql = "EXEC master.sys.xp_create_subdir '#{BACKUP_DIR_FENIX}'"
client.execute(mkdir_sql) client.execute(mkdir_sql)
puts "[+] Directorio asegurado: #{BACKUP_DIR}" puts "[+] Directorio local asegurado: #{BACKUP_DIR_FENIX}"
# Generar backup comprimido # Generar backup comprimido en disco local
puts "[1/2] Generando backup comprimido..." puts "[1/4] Generando backup en disco local..."
start = Time.now start = Time.now
result = client.execute("BACKUP DATABASE sysdasuten TO DISK = '#{BACKUP_FILE}' WITH INIT, COMPRESSION, STATS=10") result = client.execute("BACKUP DATABASE sysdasuten TO DISK = '#{BACKUP_LOCAL}' WITH INIT, COMPRESSION, STATS=10")
result.each { |r| puts " #{r['message']}" if r['message'] } result.each { |r| puts " #{r['message']}" if r['message'] }
duracion = (Time.now - start).round(2) duracion_local = (Time.now - start).round(2)
puts "[+] Backup generado en #{duracion} segundos" puts "[+] Backup local generado en #{duracion_local} segundos"
# Obtener tamaño del archivo (vía PowerShell remoto) # Verificar archivo local
puts "[2/2] Verificando archivo..." puts "[2/4] Verificando archivo local..."
size_result = client.execute("EXEC master.sys.xp_fileexist '#{BACKUP_FILE}'") file_result = client.execute("EXEC master.sys.xp_fileexist '#{BACKUP_LOCAL}'")
file_info = client.execute("SELECT size = (SELECT size FROM sys.database_files WHERE name = 'sysdasuten')") puts "[+] Archivo local verificado: #{BACKUP_LOCAL}"
puts "[+] Archivo verificado: #{BACKUP_FILE}" # Copiar a unidad de red X: (share de srv-ns8)
puts "[3/4] Copiando a unidad de red X: (srv-ns8)..."
begin
copy_ps = <<~PSCMD.strip
Copy-Item -Path "#{BACKUP_LOCAL}" -Destination "#{BACKUP_NS8}" -Force
Write-Host "Copiado: #{BACKUP_FILE} → X:\\#{BACKUP_FILE}"
PSCMD
client.execute("EXEC xp_cmdshell 'powershell -Command \"#{copy_ps.gsub('"', '\"')}\"'")
puts "[+] Copia a share completada: #{BACKUP_NS8}"
rescue => e
puts "[!] Advertencia: No se pudo copiar a X: - #{e.message}"
end
# Verificar archivo en share
puts "[4/4] Verificando archivo en share..."
begin
verify_ps = <<~PSCMD.strip
if (Test-Path "#{BACKUP_NS8}") {
$size = (Get-Item "#{BACKUP_NS8}").Length
Write-Host "Verificado: #{BACKUP_NS8} ($([math]::Round($size/1MB, 2)) MB)"
} else {
Write-Host "ERROR: No existe #{BACKUP_NS8}"
exit 1
}
PSCMD
client.execute("EXEC xp_cmdshell 'powershell -Command \"#{verify_ps.gsub('"', '\"')}\"'")
rescue => e
puts "[!] Advertencia: No se pudo verificar en X: - #{e.message}"
end
duracion_total = (Time.now - start).round(2)
# Output para orquestador # Output para orquestador
output = { output = {
paso: 'export_full_complete', paso: 'export_full_complete',
backup_ruta: BACKUP_FILE, backup_ruta_fenix: BACKUP_LOCAL,
backup_ruta_ns8: "/mnt/ns8Disco2/bkps-dasuten/#{File.basename(BACKUP_FILE)}", backup_ruta_ns8: BACKUP_NS8,
backup_ruta_ns8_unix: "/mnt/ns8Disco2/bkps-dasuten/#{BACKUP_FILE}",
backup_tipo: 'full', backup_tipo: 'full',
nodo: 'srvv-fenix', nodo: 'srvv-fenix',
nodo_destino: 'srv-ns8', nodo_destino: 'srv-ns8',
duracion: duracion, duracion_total: duracion_total,
timestamp: Time.now.iso8601, timestamp: Time.now.iso8601,
siguiente_paso: 'transferir' siguiente_paso: 'upload_drive'
} }
puts "\n✅ Export completado" puts "\n✅ Export completado (doble ubicación: local + share)"
puts " Output: #{output.to_json}" puts " Output: #{output.to_json}"
# Escribir output para que el orquestador lo lea # Escribir output para que el orquestador lo lea
@@ -4,95 +4,66 @@
# ========================================================== # ==========================================================
# adn/tools/cli/drones/dasuten_transferir_srvv-fenix-srv-ns8.rb # adn/tools/cli/drones/dasuten_transferir_srvv-fenix-srv-ns8.rb
# ========================================================== # ==========================================================
# Dron: Transferencia SMB desde srvv-fenix a srv-ns8 # Dron: Preparar backup desde share de srv-ns8 para upload
# Flujo: srvv-fenix (E:\BK_SQL\) → srv-ns8 (/var/tmp/) # Flujo: /mnt/ns8Disco2/bkps-dasuten/ → /var/tmp/ (para rclone)
# Usa: smbclient con credenciales de candados # Nota: El backup ya está en el share gracias al dron de export
# ========================================================== # ==========================================================
require 'time' require 'time'
require 'json' require 'json'
require 'shellwords' require 'fileutils'
require 'pathname'
# Ruta por defecto (backup completo)
BACKUP_REMOTO_DEFAULT = "sysdasuten_compressed_ADN.bak"
BACKUP_LOCAL_DEFAULT = "/var/tmp/sysdasuten_compressed_ADN.bak"
# Si existe output del dron anterior (export o export_dif), leer la ruta # Si existe output del dron anterior (export o export_dif), leer la ruta
EXPORT_OUTPUT_FILE = '/tmp/dron_export_output.json' EXPORT_OUTPUT_FILE = '/tmp/dron_export_output.json'
EXPORT_DIF_OUTPUT_FILE = '/tmp/dron_export_dif_output.json' EXPORT_DIF_OUTPUT_FILE = '/tmp/dron_export_dif_output.json'
backup_remoto_name = BACKUP_REMOTO_DEFAULT backup_ns8_unix = nil
backup_local = BACKUP_LOCAL_DEFAULT backup_name = nil
if File.exist?(EXPORT_DIF_OUTPUT_FILE) if File.exist?(EXPORT_DIF_OUTPUT_FILE)
output = JSON.parse(File.read(EXPORT_DIF_OUTPUT_FILE)) output = JSON.parse(File.read(EXPORT_DIF_OUTPUT_FILE))
if output['backup_ruta'] if output['backup_ruta_ns8'] || output['backup_ruta_ns8_unix']
# Extraer solo el nombre del archivo de la ruta completa (E:\BK_SQL\archivo.bak) backup_ns8_unix = output['backup_ruta_ns8_unix'] || output['backup_ruta_ns8']
# Usar split para manejar tanto backslashes como forward slashes backup_name = File.basename(backup_ns8_unix)
backup_remoto_name = output['backup_ruta'].split(/[\\\/]/).last puts "[*] Dron: Preparar backup DIFERENCIAL para upload"
backup_local = "/var/tmp/#{backup_remoto_name}"
puts "[*] Dron: Transferencia SMB srvv-fenix → srv-ns8 (DIFERENCIAL)"
end end
elsif File.exist?(EXPORT_OUTPUT_FILE) elsif File.exist?(EXPORT_OUTPUT_FILE)
output = JSON.parse(File.read(EXPORT_OUTPUT_FILE)) output = JSON.parse(File.read(EXPORT_OUTPUT_FILE))
if output['backup_ruta'] if output['backup_ruta_ns8'] || output['backup_ruta_ns8_unix']
backup_remoto_name = output['backup_ruta'].split(/[\\\/]/).last backup_ns8_unix = output['backup_ruta_ns8_unix'] || output['backup_ruta_ns8']
backup_local = "/var/tmp/#{backup_remoto_name}" backup_name = File.basename(backup_ns8_unix)
puts "[*] Dron: Transferencia SMB srvv-fenix → srv-ns8 (COMPLETO)" puts "[*] Dron: Preparar backup COMPLETO para upload"
end end
end end
BACKUP_SERVER = "//10.0.10.200/BK_SQL" unless backup_ns8_unix
BACKUP_FILE = backup_remoto_name puts "[!] Error: No se encontró ruta de backup en output de export"
exit 1
end
# Directorio de destino en srv-ns8 (compartido para backups DASUTEN)
# Nota: Este directorio está expuesto vía Samba para acceso desde srvv-fenix
BACKUP_NS8_DIR = "/mnt/ns8Disco2/bkps-dasuten" BACKUP_NS8_DIR = "/mnt/ns8Disco2/bkps-dasuten"
BACKUP_LOCAL = "/var/tmp/#{backup_name}"
puts "[*] Dron: Transferencia SMB srvv-fenix → srv-ns8" puts " Share: #{BACKUP_NS8_DIR}"
puts " Share: #{BACKUP_SERVER}" puts " Archivo: #{backup_name}"
puts " Archivo: #{BACKUP_FILE}" puts " Destino: #{BACKUP_LOCAL}"
puts " Destino: #{backup_local}"
begin begin
# Autorizar candados primero puts "\n[1/2] Copiando desde share a /var/tmp..."
system("ruby adn/tools/candados/candados.rb authorize >/dev/null 2>&1")
# Obtener credencial de candados
pass_cmd = "ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null"
pass = `#{pass_cmd}`.strip
if pass.empty?
puts "[!] No se pudo obtener credencial de candados"
exit 1
end
# Transferencia SMB
puts "\n[1/2] Descargando vía SMB..."
start = Time.now start = Time.now
# Escapar contraseña para smbclient (evitar problemas con $ y caracteres especiales) # Copiar archivo del share al directorio temporal para rclone
pass_escaped = pass.gsub('$', '\\$') FileUtils.cp(backup_ns8_unix, BACKUP_LOCAL)
cmd_smb = "smbclient #{BACKUP_SERVER} -U \"monlaricardo%#{pass_escaped}\" -W UTNlarioja -c \"lcd /var/tmp; get #{BACKUP_FILE}\" 2>&1"
system(cmd_smb)
if $?.success? duracion = (Time.now - start).round(2)
duracion = (Time.now - start).round(2) puts "[+] Copia completada en #{duracion} segundos"
puts "[+] Transferencia completada en #{duracion} segundos"
else
puts "[!] Error en transferencia SMB (código: #{$?.exitstatus})"
exit 1
end
# Verificar archivo local # Verificar archivo local
puts "\n[2/2] Verificando archivo..." puts "\n[2/2] Verificando archivo..."
if File.exist?(backup_local) if File.exist?(BACKUP_LOCAL)
tamaño_mb = (File.size(backup_local) / 1024.0 / 1024.0).round(2) tamaño_mb = (File.size(BACKUP_LOCAL) / 1024.0 / 1024.0).round(2)
puts "[+] Archivo verificado: #{tamaño_mb} MB" puts "[+] Archivo verificado: #{tamaño_mb} MB"
puts " Nota: Para copiar al share SMB, ejecutar desde srvv-fenix:" puts " Ruta: #{BACKUP_LOCAL}"
puts " net use X: \\\\10.0.10.8\\bkps-dasuten /user:rmonla <password>"
puts " copy #{backup_local} X:\\#{backup_remoto_name}"
else else
puts "[!] Archivo no encontrado en destino" puts "[!] Archivo no encontrado en destino"
exit 1 exit 1
@@ -101,20 +72,21 @@ begin
# Output para orquestador # Output para orquestador
resultado = { resultado = {
paso: 'transfer_complete', paso: 'transfer_complete',
backup_local: backup_local, backup_local: BACKUP_LOCAL,
backup_ns8_share: "#{BACKUP_NS8_DIR}/#{backup_remoto_name}", backup_ns8_share: backup_ns8_unix,
nodo: 'srv-ns8', nodo: 'srv-ns8',
duracion: duracion, duracion: duracion,
timestamp: Time.now.iso8601, timestamp: Time.now.iso8601,
siguiente_paso: 'upload_drive' siguiente_paso: 'upload_drive'
} }
puts "\nTransferencia completada" puts "\nBackup listo para upload"
puts " Output: #{resultado.to_json}" puts " Output: #{resultado.to_json}"
File.write('/tmp/dron_transfer_output.json', resultado.to_json) File.write('/tmp/dron_transfer_output.json', resultado.to_json)
rescue => e rescue => e
puts "[!] Error fatal: #{e.message}" puts "[!] Error fatal: #{e.message}"
puts e.backtrace.first(5).join("\n")
exit 1 exit 1
end end