[A04.P006] Drones de exportación actualizados con formato estandarizado

Cambios realizados:

1. dasuten_exportar_srvv-fenix.rb (FULL):
   - Nuevo formato: E:\BK_SQL\sysdasuten\sysdasuten_FULL_YYYYMMDD_HHMMSS.bak
   - Crea directorio con xp_create_subdir si no existe
   - Usa compresión nativa de SQL Server (WITH COMPRESSION)
   - Output: backup_tipo='full'

2. dasuten_exportar_diferencial_srvv-fenix.rb (DIF):
   - Nuevo formato: E:\BK_SQL\sysdasuten\sysdasuten_DIF_YYYYMMDD_HHMMSS.bak
   - Crea directorio si no existe
   - Usa compresión nativa (WITH DIFFERENTIAL, COMPRESSION)
   - Output: backup_tipo='differential'

3. dasuten_download_drive-dasu-sql4.rb:
   - Lee nombre de archivo desde upload_output JSON
   - Guarda como F:\BACKUP\{nombre_real_del_archivo}.bak
   - Soporta formatos FULL y DIF

4. dasuten_restaurar_dasu-sql4.rb:
   - Lee backup_local desde download_output JSON
   - Busca patrón sysdasuten_FULL_*.bak si no hay output
   - Encuentra el más reciente por LastWriteTime

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Ricardo Monla
2026-04-14 17:48:05 -03:00
co-authored by Claude Opus 4.6
parent 358cee8e9a
commit 0f9ebe9ef5
4 changed files with 676 additions and 0 deletions
@@ -0,0 +1,199 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# ==========================================================
# adn/tools/cli/drones/dasuten_restaurar_dasu-sql4.rb
# ==========================================================
# Dron: Restaurar backup en dasu-sql4
# Flujo: RESTORE DATABASE (sin DBCC CHECKDB)
# Entrada: Backup en F:\BACKUP\
# Salida: restore_complete (listo para verificación)
# ==========================================================
require 'time'
require 'json'
require 'base64'
require 'tempfile'
require 'shellwords'
DOWNLOAD_OUTPUT = '/tmp/dron_download_output.json'
BACKUP_DIR_WINDOWS = 'F:\\BACKUP'
# Leer ruta del backup desde el output del download
if File.exist?(DOWNLOAD_OUTPUT)
output = JSON.parse(File.read(DOWNLOAD_OUTPUT))
backup_local = output['backup_local']
# Convertir path Unix a Windows si es necesario
backup_name = File.basename(backup_local)
LOCAL_BACKUP = "#{BACKUP_DIR_WINDOWS}\\#{backup_name}"
puts "[*] Dron: Restaurar backup DASUTEN (COMPLETO)"
else
LOCAL_BACKUP = "#{BACKUP_DIR_WINDOWS}\\sysdasuten_FULL_*.bak"
puts "[*] Dron: Restaurar backup DASUTEN (COMPLETO - buscar más reciente)"
end
TARGET_IP = "192.168.1.11"
TARGET_USER = "Administrador"
TARGET_PASS = "UTNlarioja00DASU"
puts " Nodo: dasu-sql4"
puts " Backup: #{LOCAL_BACKUP}"
# PowerShell script para restore (sin checkdb)
ps_script = <<~'PSCMD'
$ErrorActionPreference = "Stop"
$BACKUP_DIR = "F:\BACKUP"
$BACKUP_PATTERN = "sysdasuten_FULL_*.bak"
$JSON_PATH = "C:\temp\dron_restore_output.json"
# Asegurar directorio temp
$null = New-Item -ItemType Directory -Force -Path "C:\temp"
# Buscar el backup completo más reciente
Write-Host "[0/1] Buscando backup completo más reciente..."
$backupFile = Get-ChildItem "$BACKUP_DIR\$BACKUP_PATTERN" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
if (-not $backupFile) {
Write-Host "[!] No se encontró backup completo en $BACKUP_DIR"
exit 1
}
$LOCAL_BACKUP = $backupFile.FullName
Write-Host " Archivo: $LOCAL_BACKUP"
Write-Host " Tamaño: $([math]::Round($backupFile.Length / 1MB, 2)) MB"
Write-Host "[1/1] Ejecutando RESTORE DATABASE..."
$start = Get-Date
# Ruta completa a sqlcmd (SQL Server 2019)
$sqlcmd = "C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\sqlcmd.exe"
# Comando RESTORE
$restoreSql = @"
RESTORE DATABASE sysdasuten
FROM DISK = '$LOCAL_BACKUP'
WITH REPLACE,
MOVE 'sysdasuten' TO 'F:\DATA\sysdasuten.mdf',
MOVE 'sysdasuten_log' TO 'F:\LOG\sysdasuten_log.ldf'
"@
Write-Host " SQL: $restoreSql"
# Ejecutar restore
$restoreOutput = & $sqlcmd -S localhost -Q $restoreSql 2>&1
$exitoRestore = $LASTEXITCODE -eq 0
$duracionRestore = [math]::Round((New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds, 2)
if (-not $exitoRestore) {
Write-Host "[!] Error en restore:"
Write-Host $restoreOutput
exit 1
}
Write-Host "[+] Restore completado en $duracionRestore segundos"
$duracionTotal = $duracionRestore
# Output JSON
$res = @{
paso = "restore_complete"
backup_ruta = $LOCAL_BACKUP
nodo = "dasu-sql4"
duracion_total = $duracionTotal
duracion_restore = $duracionRestore
estado_integridad = "PENDING_CHECKDB"
timestamp = (Get-Date -Format "o")
siguiente_paso = "verificar_integridad"
}
Write-Host "`n✅ Restauración completada (pendiente CHECKDB)"
Write-Host " Output: $($res | ConvertTo-Json -Compress)"
$res | ConvertTo-Json | Out-File -FilePath $JSON_PATH -Encoding utf8
PSCMD
# Codificar script en base64
ps_encoded = Base64.strict_encode64(ps_script)
# Script bash que se ejecuta en srv-dasu
bash_script = <<~BASHHEREDOC
#!/bin/bash
export SSHPASS="#{TARGET_PASS}"
sshpass -e ssh -o StrictHostKeyChecking=no -p 7022 #{TARGET_USER}@#{TARGET_IP} \
"powershell -Command \\\"Invoke-Expression ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('#{ps_encoded}')))\\\""
BASHHEREDOC
# Guardar script bash en archivo temporal
bash_file = Tempfile.new(['restore', '.sh'])
bash_file.write(bash_script)
bash_file.chmod(0755)
bash_file.close
remote_bash_path = "/tmp/dron_restore_#{Time.now.to_i}.sh"
puts "\n[1/3] Copiando script a srv-dasu..."
scp_cmd = "scp -o StrictHostKeyChecking=no #{bash_file.path} srv-dasu:#{remote_bash_path}"
unless system(scp_cmd)
puts "[!] Error al copiar script"
bash_file.unlink
exit 1
end
puts "\n[2/3] Ejecutando en dasu-sql4 vía srv-dasu..."
ssh_cmd = "ssh srv-dasu 'bash #{remote_bash_path}'"
output = `#{ssh_cmd}`
output = output.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') if output
puts output
# Limpieza
bash_file.unlink
system("ssh srv-dasu 'rm -f #{remote_bash_path}' 2>/dev/null")
if $?.success?
puts "\n[3/3] Leyendo output JSON..."
json_ps = 'Get-Content C:\temp\dron_restore_output.json -Raw'
json_encoded = Base64.strict_encode64(json_ps)
json_script = <<~JSONBASH.strip
#!/bin/bash
export SSHPASS="#{TARGET_PASS}"
sshpass -e ssh -o StrictHostKeyChecking=no -p 7022 #{TARGET_USER}@#{TARGET_IP} \
"powershell -Command \\\"Invoke-Expression ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('#{json_encoded}')))\\\""
JSONBASH
json_file = Tempfile.new(['json', '.sh'])
json_file.write(json_script)
json_file.chmod(0755)
json_file.close
remote_json_path = "/tmp/dron_json_#{Time.now.to_i}.sh"
system("scp -o StrictHostKeyChecking=no #{json_file.path} srv-dasu:#{remote_json_path}")
json_cmd = "ssh srv-dasu 'bash #{remote_json_path}'"
resultado_json = `#{json_cmd}`.strip
resultado_json = resultado_json.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') if resultado_json
json_file.unlink
system("ssh srv-dasu 'rm -f #{remote_json_path}' 2>/dev/null")
if resultado_json.empty?
puts "[!] No se pudo leer output JSON"
exit 1
end
json_match = resultado_json.match(/\{.*\}/m)
resultado_json = json_match ? json_match[0] : resultado_json
puts " Output: #{resultado_json}"
resultado = JSON.parse(resultado_json)
puts "\n✅ Restauración completada"
puts " Backup: #{resultado['backup_ruta']}"
puts " Estado: #{resultado['estado_integridad']}"
puts " Duración: #{resultado['duracion_total']}s"
File.write('/tmp/dron_restore_output.json', resultado_json)
puts " JSON guardado en: /tmp/dron_restore_output.json"
else
puts "[!] Error en ejecución (código: #{$?.exitstatus})"
exit 1
end