[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:
co-authored by
Claude Opus 4.6
parent
358cee8e9a
commit
0f9ebe9ef5
+268
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# ==========================================================
|
||||
# adn/tools/cli/drones/dasuten_download_drive-dasu-sql4.rb
|
||||
# ==========================================================
|
||||
# Dron: Download desde Google Drive a dasu-sql4
|
||||
# Flujo: Google Drive → F:\BACKUP\sysdasuten_compressed_ADN.bak
|
||||
# Usa: Invoke-WebRequest (sin rclone)
|
||||
# ==========================================================
|
||||
|
||||
require 'time'
|
||||
require 'json'
|
||||
require 'shellwords'
|
||||
require 'optparse'
|
||||
require 'base64'
|
||||
require 'tempfile'
|
||||
|
||||
opciones = {
|
||||
test_file: nil,
|
||||
file_id: nil # Google Drive file ID
|
||||
}
|
||||
|
||||
OptionParser.new do |opts|
|
||||
opts.banner = "Uso: dron lanzar --nota 'Download' --nodo dasu-sql4 -- ruby dasuten_download_drive.rb [opciones]"
|
||||
opts.on("--test-file ARCHIVO", "Archivo específico para test (ej: test_Backup.bak)") { |f| opciones[:test_file] = f }
|
||||
opts.on("--file-id ID", "Google Drive file ID") { |id| opciones[:file_id] = id }
|
||||
end.parse!(ARGV)
|
||||
|
||||
BACKUP_DIR_WINDOWS = "F:\\BACKUP"
|
||||
BACKUP_NAME_DEFAULT = "sysdasuten_compressed_ADN.bak"
|
||||
TARGET_IP = "192.168.1.11"
|
||||
TARGET_USER = "Administrador"
|
||||
TARGET_PASS = "UTNlarioja00DASU"
|
||||
|
||||
# Determinar nombre del archivo backup
|
||||
LOCAL_BACKUP = "#{BACKUP_DIR_WINDOWS}\\#{BACKUP_NAME_DEFAULT}"
|
||||
|
||||
puts "[*] Dron: Download desde Google Drive"
|
||||
puts " Nodo: dasu-sql4"
|
||||
|
||||
# Determinar archivo a descargar
|
||||
if opciones[:test_file]
|
||||
puts " 🧪 MODO TEST: Descargando archivo específico"
|
||||
# File ID conocido para test_Backup.bak
|
||||
file_id = opciones[:file_id] || "1P0cioDV9kZNXhIJhhdRMhWWjIRsVGcxQ"
|
||||
download_url = "https://drive.google.com/uc?export=download&id=#{file_id}"
|
||||
puts " File ID: #{file_id}"
|
||||
puts " Destino: #{LOCAL_BACKUP}"
|
||||
elsif opciones[:file_id]
|
||||
puts " Download por file ID"
|
||||
download_url = "https://drive.google.com/uc?export=download&id=#{opciones[:file_id]}"
|
||||
puts " File ID: #{opciones[:file_id]}"
|
||||
puts " Destino: #{LOCAL_BACKUP}"
|
||||
else
|
||||
# Intentar leer file_id desde el output del upload
|
||||
UPLOAD_OUTPUT = '/tmp/dron_upload_output.json'
|
||||
if File.exist?(UPLOAD_OUTPUT)
|
||||
upload_data = JSON.parse(File.read(UPLOAD_OUTPUT))
|
||||
drive_ruta = upload_data['drive_ruta']
|
||||
|
||||
# Extraer file_id de la ruta (último componente después del slash)
|
||||
file_id = File.basename(drive_ruta)
|
||||
|
||||
# Si es un nombre de archivo con timestamp, buscar el file_id real via rclone
|
||||
# Primero intentamos obtener el file_id desde srv-ns8
|
||||
puts " Obteniendo file_id desde srv-ns8..."
|
||||
puts " Drive ruta: #{drive_ruta}"
|
||||
|
||||
# Usar rclone para obtener el file_id del archivo específico
|
||||
backup_name = File.basename(drive_ruta)
|
||||
drive_folder = drive_ruta.rpartition('/').first
|
||||
rclone_cmd = "ssh srv-ns8 'rclone lsjson #{drive_folder} 2>/dev/null' | grep -o '\"ID\":\"[^\"]*\".*\"Name\":\"#{backup_name}\"' | head -1 | grep -o '\"ID\":\"[^\"]*\"' | cut -d'\"' -f4"
|
||||
file_id_result = `#{rclone_cmd}`.strip
|
||||
|
||||
if file_id_result && !file_id_result.empty?
|
||||
file_id = file_id_result
|
||||
puts " File ID obtenido: #{file_id}"
|
||||
else
|
||||
puts " [!] No se pudo obtener file_id, usando nombre de archivo"
|
||||
file_id = File.basename(drive_ruta)
|
||||
end
|
||||
|
||||
download_url = "https://drive.google.com/uc?export=download&id=#{file_id}"
|
||||
backup_name = file_id # Usar el nombre del archivo desde drive_ruta
|
||||
puts " Download URL: #{download_url}"
|
||||
puts " Backup nombre: #{backup_name}"
|
||||
puts " Destino: #{BACKUP_DIR_WINDOWS}\\#{backup_name}"
|
||||
else
|
||||
puts " [!] Error: Se requiere --test-file o --file-id"
|
||||
puts " Para produccion: obtener file_id con rclone lsjson desde srv-ns8"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
# PowerShell script para download
|
||||
# Para archivos grandes, Google Drive muestra página de confirmación de virus.
|
||||
# Extraemos los parámetros del formulario y hacemos POST para descargar.
|
||||
ps_script = <<~'PSCMD'
|
||||
$ErrorActionPreference = "Stop"
|
||||
$BACKUP_DIR = "F:\BACKUP"
|
||||
$DOWNLOAD_URL = "DOWNLOAD_URL_PLACEHOLDER"
|
||||
$BACKUP_NAME = "BACKUP_NAME_PLACEHOLDER"
|
||||
$LOCAL_BACKUP = "$BACKUP_DIR\$BACKUP_NAME"
|
||||
$JSON_PATH = "C:\temp\dron_download_output.json"
|
||||
|
||||
# Asegurar directorios
|
||||
$null = New-Item -ItemType Directory -Force -Path "C:\temp"
|
||||
$null = New-Item -ItemType Directory -Force -Path $BACKUP_DIR
|
||||
|
||||
Write-Host "[1/3] Iniciando descarga..."
|
||||
Write-Host " URL: $DOWNLOAD_URL"
|
||||
|
||||
Write-Host "[2/3] Descargando..."
|
||||
$start = Get-Date
|
||||
try {
|
||||
# Paso 1: Obtener página de confirmación
|
||||
$response = Invoke-WebRequest -Uri $DOWNLOAD_URL -UseBasicParsing -Headers @{
|
||||
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
}
|
||||
|
||||
# Extraer parámetros del formulario de confirmación
|
||||
$html = $response.Content
|
||||
$fileId = ""
|
||||
$uuid = ""
|
||||
$confirm = "t"
|
||||
if ($html -match 'name="id" value="([^"]+)"') { $fileId = $matches[1] }
|
||||
if ($html -match 'name="uuid" value="([^"]+)"') { $uuid = $matches[1] }
|
||||
if ($html -match 'name="confirm" value="([^"]+)"') { $confirm = $matches[1] }
|
||||
|
||||
Write-Host " File ID: $fileId"
|
||||
Write-Host " UUID: $uuid"
|
||||
|
||||
# Paso 2: Construir URL de descarga con confirmación
|
||||
$downloadConfirmUrl = "https://drive.usercontent.google.com/download?id=$fileId&export=download&confirm=$confirm&uuid=$uuid"
|
||||
Write-Host " URL confirmada: $downloadConfirmUrl"
|
||||
|
||||
# Paso 3: Descargar archivo real
|
||||
$finalResponse = Invoke-WebRequest -Uri $downloadConfirmUrl -UseBasicParsing -Headers @{
|
||||
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
}
|
||||
|
||||
# Guardar contenido
|
||||
[System.IO.File]::WriteAllBytes($LOCAL_BACKUP, $finalResponse.Content)
|
||||
|
||||
$dur = (New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds
|
||||
Write-Host "[+] Descarga en $([math]::Round($dur, 2))s"
|
||||
} catch {
|
||||
Write-Host "[!] Error: $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "[3/3] Verificando..."
|
||||
if (Test-Path $LOCAL_BACKUP) {
|
||||
$tam = (Get-Item $LOCAL_BACKUP).Length
|
||||
Write-Host " Tamano: $([math]::Round($tam / 1MB, 2)) MB"
|
||||
if ($tam -eq 0) {
|
||||
Write-Host "[!] Archivo vacío"
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
Write-Host "[!] No existe"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$res = @{
|
||||
paso = "download_complete"
|
||||
backup_local = $LOCAL_BACKUP
|
||||
drive_origen = "Google Drive"
|
||||
nodo = "dasu-sql4"
|
||||
duracion = [math]::Round($dur, 2)
|
||||
timestamp = (Get-Date -Format "o")
|
||||
siguiente_paso = "restaurar_backup"
|
||||
}
|
||||
$res | ConvertTo-Json | Out-File -FilePath $JSON_PATH -Encoding utf8
|
||||
Write-Host " Output: $($res | ConvertTo-Json -Compress)"
|
||||
PSCMD
|
||||
|
||||
# Reemplazar placeholders con valores reales
|
||||
ps_script = ps_script.gsub('DOWNLOAD_URL_PLACEHOLDER', download_url)
|
||||
ps_script = ps_script.gsub('BACKUP_NAME_PLACEHOLDER', backup_name)
|
||||
|
||||
# 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(['download', '.sh'])
|
||||
bash_file.write(bash_script)
|
||||
bash_file.chmod(0755)
|
||||
bash_file.close
|
||||
|
||||
remote_bash_path = "/tmp/dron_download_#{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_download_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✅ Download completado"
|
||||
puts " Backup: #{resultado['backup_local']}"
|
||||
puts " Duración: #{resultado['duracion']}s"
|
||||
|
||||
File.write('/tmp/dron_download_output.json', resultado_json)
|
||||
puts " JSON guardado en: /tmp/dron_download_output.json"
|
||||
else
|
||||
puts "[!] Error en ejecución (código: #{$?.exitstatus})"
|
||||
exit 1
|
||||
end
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# ==========================================================
|
||||
# adn/tools/cli/drones/dasuten_exportar_diferencial_srvv-fenix.rb
|
||||
# ==========================================================
|
||||
# Dron: Exportar backup DIFERENCIAL de DASUTEN en srvv-fenix
|
||||
# Flujo: srvv-fenix → Backup diferencial en E:\BK_SQL\
|
||||
# Salida: Marca evento como 'export_complete' con ruta del backup
|
||||
# ==========================================================
|
||||
|
||||
require 'tiny_tds'
|
||||
require 'time'
|
||||
require 'json'
|
||||
|
||||
# Nombre del archivo con timestamp
|
||||
TIMESTAMP = Time.now.strftime('%Y%m%d_%H%M%S')
|
||||
BACKUP_DIR = "E:\\BK_SQL\\sysdasuten"
|
||||
BACKUP_FILE = "#{BACKUP_DIR}\\sysdasuten_DIF_#{TIMESTAMP}.bak"
|
||||
|
||||
puts "[*] Dron: Exportar backup DIFERENCIAL DASUTEN"
|
||||
puts " Nodo: srvv-fenix"
|
||||
puts " Destino: #{BACKUP_FILE}"
|
||||
puts " Tipo: DIFFERENTIAL (solo cambios desde último backup completo)"
|
||||
puts " Directorio: #{BACKUP_DIR}"
|
||||
|
||||
begin
|
||||
# Autorizar candados primero (necesario para acceder a la bóveda)
|
||||
system("ruby adn/tools/candados/candados.rb authorize >/dev/null 2>&1")
|
||||
|
||||
# Obtener credencial de candados (patrón usado en otros drones)
|
||||
pass_cmd = "ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null"
|
||||
sql_pass = `#{pass_cmd}`.strip
|
||||
|
||||
sql_user = 'UTNlarioja\\monlaricardo'
|
||||
sql_host = '10.0.10.200'
|
||||
|
||||
if sql_pass.empty?
|
||||
puts "[!] Error: No se pudo obtener credencial de candados (utnlarioja.intranet:monlaricardo)"
|
||||
exit 1
|
||||
end
|
||||
|
||||
client = TinyTds::Client.new(
|
||||
username: sql_user,
|
||||
password: sql_pass,
|
||||
host: sql_host,
|
||||
timeout: 600
|
||||
)
|
||||
puts "[+] Conectado a SQL Server en #{sql_host} (auth: Windows)"
|
||||
|
||||
# Crear directorio si no existe
|
||||
puts "\n[0/3] Verificando directorio..."
|
||||
mkdir_sql = "EXEC master.sys.xp_create_subdir '#{BACKUP_DIR}'"
|
||||
client.execute(mkdir_sql)
|
||||
puts "[+] Directorio asegurado: #{BACKUP_DIR}"
|
||||
|
||||
# Generar backup diferencial
|
||||
puts "\n[1/2] Generando backup diferencial..."
|
||||
start = Time.now
|
||||
|
||||
sql = <<~SQL.strip
|
||||
BACKUP DATABASE sysdasuten
|
||||
TO DISK = '#{BACKUP_FILE}'
|
||||
WITH DIFFERENTIAL, COMPRESSION, STATS=10
|
||||
SQL
|
||||
|
||||
puts " SQL: #{sql}"
|
||||
|
||||
result = client.execute(sql)
|
||||
result.each { |r| puts " #{r['message']}" if r['message'] }
|
||||
|
||||
duracion = (Time.now - start).round(2)
|
||||
puts "[+] Backup diferencial generado en #{duracion} segundos"
|
||||
|
||||
# Obtener tamaño del archivo (vía PowerShell remoto o xp_fileexist)
|
||||
puts "\n[2/2] Verificando archivo..."
|
||||
|
||||
begin
|
||||
file_result = client.execute("EXEC master.sys.xp_fileexist '#{BACKUP_FILE}'")
|
||||
file_info = file_result.first
|
||||
|
||||
if file_info && file_result.to_a.any? { |f| f['File Exists'] == 1 }
|
||||
puts "[+] Archivo verificado: #{BACKUP_FILE}"
|
||||
|
||||
# Obtener tamaño aproximado
|
||||
size_info = client.execute("SELECT size_bytes = (SELECT size * 8192 FROM sys.database_files WHERE name = 'sysdasuten')")
|
||||
puts " Tamaño BD base: #{size_info.to_a.first['size_bytes'].to_i / 1024 / 1024} MB"
|
||||
else
|
||||
puts "[!] Advertencia: No se pudo verificar existencia del archivo"
|
||||
end
|
||||
rescue => e
|
||||
puts "[!] Advertencia: #{e.message}"
|
||||
end
|
||||
|
||||
# Output para orquestador
|
||||
output = {
|
||||
paso: 'export_differential_complete',
|
||||
backup_ruta: BACKUP_FILE,
|
||||
backup_tipo: 'differential',
|
||||
nodo: 'srvv-fenix',
|
||||
duracion: duracion,
|
||||
timestamp: Time.now.iso8601,
|
||||
siguiente_paso: 'transferir'
|
||||
}
|
||||
|
||||
puts "\n✅ Export diferencial completado"
|
||||
puts " Output: #{output.to_json}"
|
||||
|
||||
# Escribir output para que el orquestador lo lea
|
||||
File.write('/tmp/dron_export_dif_output.json', output.to_json)
|
||||
|
||||
rescue => e
|
||||
puts "[!] Error fatal: #{e.message}"
|
||||
puts e.backtrace.first(5).join("\n")
|
||||
exit 1
|
||||
end
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# ==========================================================
|
||||
# adn/tools/cli/drones/dasuten_exportar_srvv-fenix.rb
|
||||
# ==========================================================
|
||||
# Dron: Exportar backup en srvv-fenix
|
||||
# Flujo: srvv-fenix → Backup comprimido en E:\BK_SQL\
|
||||
# Salida: Marca evento como 'export_complete' con ruta del backup
|
||||
# ==========================================================
|
||||
|
||||
require 'tiny_tds'
|
||||
require 'time'
|
||||
require 'json'
|
||||
|
||||
# Nombre del archivo con timestamp
|
||||
TIMESTAMP = Time.now.strftime('%Y%m%d_%H%M%S')
|
||||
BACKUP_DIR = "E:\\BK_SQL\\sysdasuten"
|
||||
BACKUP_FILE = "#{BACKUP_DIR}\\sysdasuten_FULL_#{TIMESTAMP}.bak"
|
||||
|
||||
puts "[*] Dron: Exportar backup COMPLETO DASUTEN"
|
||||
puts " Nodo: srvv-fenix"
|
||||
puts " Destino: #{BACKUP_FILE}"
|
||||
puts " Tipo: FULL (completo)"
|
||||
|
||||
begin
|
||||
# Autorizar candados primero (necesario para acceder a la bóveda)
|
||||
system("ruby adn/tools/candados/candados.rb authorize >/dev/null 2>&1")
|
||||
|
||||
# Obtener credencial de candados (patrón usado en otros drones)
|
||||
pass_cmd = "ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null"
|
||||
sql_pass = `#{pass_cmd}`.strip
|
||||
|
||||
sql_user = 'UTNlarioja\\monlaricardo'
|
||||
sql_host = '10.0.10.200'
|
||||
|
||||
if sql_pass.empty?
|
||||
puts "[!] Error: No se pudo obtener credencial de candados (utnlarioja.intranet:monlaricardo)"
|
||||
exit 1
|
||||
end
|
||||
|
||||
client = TinyTds::Client.new(
|
||||
username: sql_user,
|
||||
password: sql_pass,
|
||||
host: sql_host,
|
||||
timeout: 600
|
||||
)
|
||||
puts "[+] Conectado a SQL Server en #{sql_host} (auth: Windows)"
|
||||
|
||||
# Crear directorio si no existe
|
||||
puts "\n[0/3] Verificando directorio..."
|
||||
mkdir_sql = "EXEC master.sys.xp_create_subdir '#{BACKUP_DIR}'"
|
||||
client.execute(mkdir_sql)
|
||||
puts "[+] Directorio asegurado: #{BACKUP_DIR}"
|
||||
|
||||
# Generar backup comprimido
|
||||
puts "[1/2] Generando backup comprimido..."
|
||||
start = Time.now
|
||||
|
||||
result = client.execute("BACKUP DATABASE sysdasuten TO DISK = '#{BACKUP_FILE}' WITH INIT, COMPRESSION, STATS=10")
|
||||
result.each { |r| puts " #{r['message']}" if r['message'] }
|
||||
|
||||
duracion = (Time.now - start).round(2)
|
||||
puts "[+] Backup generado en #{duracion} segundos"
|
||||
|
||||
# Obtener tamaño del archivo (vía PowerShell remoto)
|
||||
puts "[2/2] Verificando archivo..."
|
||||
size_result = client.execute("EXEC master.sys.xp_fileexist '#{BACKUP_FILE}'")
|
||||
file_info = client.execute("SELECT size = (SELECT size FROM sys.database_files WHERE name = 'sysdasuten')")
|
||||
|
||||
puts "[+] Archivo verificado: #{BACKUP_FILE}"
|
||||
|
||||
# Output para orquestador
|
||||
output = {
|
||||
paso: 'export_full_complete',
|
||||
backup_ruta: BACKUP_FILE,
|
||||
backup_tipo: 'full',
|
||||
nodo: 'srvv-fenix',
|
||||
duracion: duracion,
|
||||
timestamp: Time.now.iso8601,
|
||||
siguiente_paso: 'transferir'
|
||||
}
|
||||
|
||||
puts "\n✅ Export completado"
|
||||
puts " Output: #{output.to_json}"
|
||||
|
||||
# Escribir output para que el orquestador lo lea
|
||||
File.write('/tmp/dron_export_output.json', output.to_json)
|
||||
|
||||
rescue => e
|
||||
puts "[!] Error fatal: #{e.message}"
|
||||
exit 1
|
||||
end
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user