[A04.P005] Extraído módulo común DasuExecutor para eliminar código repetido
Problema: 6 drones repetían el mismo patrón de ejecución remota: - Base64 encoding de scripts PowerShell - SSH vía srv-dasu con sshpass - Lectura de JSON output desde dasu-sql4 - Verificación de archivos en fenix vía xp_cmdshell Solución: adn/tools/cli/drones/lib/dasu_executor.rb Funciones del módulo: - execute_ps_on_dasu(ps_script) → ejecuta PowerShell en dasu-sql4 - read_json_from_dasu(path) → lee JSON output - execute_ps_with_json_output(ps_script) → combina ambas - execute_ps_on_fenix(client, ps_script) → PowerShell en fenix - file_exists_on_fenix?(client, path) → verifica existencia - get_file_info_on_fenix(client, path) → info completa de archivo Drones refactorizados (líneas ahorradas): - dasuten_restaurar_dasu-sql4.rb: 200 → 95 líneas (-52%) - dasuten_restaurar_diferencial_dasu-sql4.rb: 207 → 100 líneas (-52%) - dasuten_download_drive-dasu-sql4.rb: 269 → 130 líneas (-52%) - dasuten_verificar-integridad_dasu-sql4.rb: 182 → 85 líneas (-53%) - dasuten_exportar_srvv-fenix.rb: usa módulo para verificación - dasuten_exportar_diferencial_srvv-fenix.rb: usa módulo para verificación Beneficio: Si se modifica el patrón SSH/PowerShell, se cambia en 1 solo lugar.
This commit is contained in:
@@ -11,14 +11,12 @@
|
|||||||
|
|
||||||
require 'time'
|
require 'time'
|
||||||
require 'json'
|
require 'json'
|
||||||
require 'shellwords'
|
|
||||||
require 'optparse'
|
require 'optparse'
|
||||||
require 'base64'
|
require_relative 'lib/dasu_executor'
|
||||||
require 'tempfile'
|
|
||||||
|
|
||||||
opciones = {
|
opciones = {
|
||||||
test_file: nil,
|
test_file: nil,
|
||||||
file_id: nil # Google Drive file ID
|
file_id: nil
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionParser.new do |opts|
|
OptionParser.new do |opts|
|
||||||
@@ -29,83 +27,55 @@ end.parse!(ARGV)
|
|||||||
|
|
||||||
BACKUP_DIR_WINDOWS = "F:\\BACKUP"
|
BACKUP_DIR_WINDOWS = "F:\\BACKUP"
|
||||||
BACKUP_NAME_DEFAULT = "sysdasuten_compressed_ADN.bak"
|
BACKUP_NAME_DEFAULT = "sysdasuten_compressed_ADN.bak"
|
||||||
TARGET_IP = "192.168.1.11"
|
JSON_PATH = "C:\temp\dron_download_output.json"
|
||||||
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
|
# Determinar archivo a descargar
|
||||||
if opciones[:test_file]
|
if opciones[:test_file]
|
||||||
|
puts "[*] Dron: Download desde Google Drive"
|
||||||
|
puts " Nodo: dasu-sql4"
|
||||||
puts " 🧪 MODO TEST: Descargando archivo específico"
|
puts " 🧪 MODO TEST: Descargando archivo específico"
|
||||||
# File ID conocido para test_Backup.bak
|
|
||||||
file_id = opciones[:file_id] || "1P0cioDV9kZNXhIJhhdRMhWWjIRsVGcxQ"
|
file_id = opciones[:file_id] || "1P0cioDV9kZNXhIJhhdRMhWWjIRsVGcxQ"
|
||||||
download_url = "https://drive.google.com/uc?export=download&id=#{file_id}"
|
download_url = "https://drive.google.com/uc?export=download&id=#{file_id}"
|
||||||
|
backup_name = opciones[:test_file]
|
||||||
puts " File ID: #{file_id}"
|
puts " File ID: #{file_id}"
|
||||||
puts " Destino: #{LOCAL_BACKUP}"
|
puts " Destino: #{BACKUP_DIR_WINDOWS}\\#{backup_name}"
|
||||||
elsif opciones[:file_id]
|
elsif opciones[:file_id]
|
||||||
|
puts "[*] Dron: Download desde Google Drive"
|
||||||
|
puts " Nodo: dasu-sql4"
|
||||||
puts " Download por file ID"
|
puts " Download por file ID"
|
||||||
download_url = "https://drive.google.com/uc?export=download&id=#{opciones[:file_id]}"
|
download_url = "https://drive.google.com/uc?export=download&id=#{opciones[:file_id]}"
|
||||||
|
backup_name = File.basename(opciones[:file_id])
|
||||||
puts " File ID: #{opciones[:file_id]}"
|
puts " File ID: #{opciones[:file_id]}"
|
||||||
puts " Destino: #{LOCAL_BACKUP}"
|
puts " Destino: #{BACKUP_DIR_WINDOWS}\\#{backup_name}"
|
||||||
else
|
else
|
||||||
# Intentar leer file_id desde el output del upload
|
|
||||||
UPLOAD_OUTPUT = '/tmp/dron_upload_output.json'
|
UPLOAD_OUTPUT = '/tmp/dron_upload_output.json'
|
||||||
if File.exist?(UPLOAD_OUTPUT)
|
if File.exist?(UPLOAD_OUTPUT)
|
||||||
upload_data = JSON.parse(File.read(UPLOAD_OUTPUT))
|
upload_data = JSON.parse(File.read(UPLOAD_OUTPUT))
|
||||||
drive_ruta = upload_data['drive_ruta']
|
drive_ruta = upload_data['drive_ruta']
|
||||||
|
|
||||||
# Extraer file_id de la ruta (último componente después del slash)
|
|
||||||
file_id = File.basename(drive_ruta)
|
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)
|
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}"
|
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 "[*] Dron: Download desde Google Drive"
|
||||||
puts " Download URL: #{download_url}"
|
puts " Nodo: dasu-sql4"
|
||||||
puts " Backup nombre: #{backup_name}"
|
puts " Drive ruta: #{drive_ruta}"
|
||||||
|
puts " File ID: #{file_id}"
|
||||||
puts " Destino: #{BACKUP_DIR_WINDOWS}\\#{backup_name}"
|
puts " Destino: #{BACKUP_DIR_WINDOWS}\\#{backup_name}"
|
||||||
else
|
else
|
||||||
puts " [!] Error: Se requiere --test-file o --file-id"
|
puts "[!] Error: Se requiere --test-file o --file-id"
|
||||||
puts " Para produccion: obtener file_id con rclone lsjson desde srv-ns8"
|
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# PowerShell script para download
|
# PowerShell script para download
|
||||||
# Para archivos grandes, Google Drive muestra página de confirmación de virus.
|
ps_script = <<~PSCMD.strip
|
||||||
# Extraemos los parámetros del formulario y hacemos POST para descargar.
|
|
||||||
ps_script = <<~'PSCMD'
|
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
$BACKUP_DIR = "F:\BACKUP"
|
$BACKUP_DIR = "F:\\BACKUP"
|
||||||
$DOWNLOAD_URL = "DOWNLOAD_URL_PLACEHOLDER"
|
$DOWNLOAD_URL = "#{download_url}"
|
||||||
$BACKUP_NAME = "BACKUP_NAME_PLACEHOLDER"
|
$BACKUP_NAME = "#{backup_name}"
|
||||||
$LOCAL_BACKUP = "$BACKUP_DIR\$BACKUP_NAME"
|
$LOCAL_BACKUP = "$BACKUP_DIR\\$BACKUP_NAME"
|
||||||
$JSON_PATH = "C:\temp\dron_download_output.json"
|
$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 "C:\temp"
|
|
||||||
$null = New-Item -ItemType Directory -Force -Path $BACKUP_DIR
|
$null = New-Item -ItemType Directory -Force -Path $BACKUP_DIR
|
||||||
|
|
||||||
Write-Host "[1/3] Iniciando descarga..."
|
Write-Host "[1/3] Iniciando descarga..."
|
||||||
@@ -114,12 +84,10 @@ ps_script = <<~'PSCMD'
|
|||||||
Write-Host "[2/3] Descargando..."
|
Write-Host "[2/3] Descargando..."
|
||||||
$start = Get-Date
|
$start = Get-Date
|
||||||
try {
|
try {
|
||||||
# Paso 1: Obtener página de confirmación
|
|
||||||
$response = Invoke-WebRequest -Uri $DOWNLOAD_URL -UseBasicParsing -Headers @{
|
$response = Invoke-WebRequest -Uri $DOWNLOAD_URL -UseBasicParsing -Headers @{
|
||||||
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
"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
|
$html = $response.Content
|
||||||
$fileId = ""
|
$fileId = ""
|
||||||
$uuid = ""
|
$uuid = ""
|
||||||
@@ -131,16 +99,13 @@ ps_script = <<~'PSCMD'
|
|||||||
Write-Host " File ID: $fileId"
|
Write-Host " File ID: $fileId"
|
||||||
Write-Host " UUID: $uuid"
|
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"
|
$downloadConfirmUrl = "https://drive.usercontent.google.com/download?id=$fileId&export=download&confirm=$confirm&uuid=$uuid"
|
||||||
Write-Host " URL confirmada: $downloadConfirmUrl"
|
Write-Host " URL confirmada: $downloadConfirmUrl"
|
||||||
|
|
||||||
# Paso 3: Descargar archivo real
|
|
||||||
$finalResponse = Invoke-WebRequest -Uri $downloadConfirmUrl -UseBasicParsing -Headers @{
|
$finalResponse = Invoke-WebRequest -Uri $downloadConfirmUrl -UseBasicParsing -Headers @{
|
||||||
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Guardar contenido
|
|
||||||
[System.IO.File]::WriteAllBytes($LOCAL_BACKUP, $finalResponse.Content)
|
[System.IO.File]::WriteAllBytes($LOCAL_BACKUP, $finalResponse.Content)
|
||||||
|
|
||||||
$dur = (New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds
|
$dur = (New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds
|
||||||
@@ -176,93 +141,17 @@ ps_script = <<~'PSCMD'
|
|||||||
Write-Host " Output: $($res | ConvertTo-Json -Compress)"
|
Write-Host " Output: $($res | ConvertTo-Json -Compress)"
|
||||||
PSCMD
|
PSCMD
|
||||||
|
|
||||||
# Reemplazar placeholders con valores reales
|
puts "\n[1/2] Ejecutando download en dasu-sql4..."
|
||||||
ps_script = ps_script.gsub('DOWNLOAD_URL_PLACEHOLDER', download_url)
|
DasuExecutor.execute_ps_on_dasu(ps_script, output_path: JSON_PATH)
|
||||||
ps_script = ps_script.gsub('BACKUP_NAME_PLACEHOLDER', backup_name)
|
|
||||||
|
|
||||||
# Codificar script en base64
|
puts "\n[2/2] Leyendo output JSON..."
|
||||||
ps_encoded = Base64.strict_encode64(ps_script)
|
resultado = DasuExecutor.read_json_from_dasu(JSON_PATH)
|
||||||
|
|
||||||
# Script bash que se ejecuta en srv-dasu
|
puts " Output: #{resultado.to_json}"
|
||||||
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
|
puts "\n✅ Download completado"
|
||||||
bash_file = Tempfile.new(['download', '.sh'])
|
puts " Backup: #{resultado['backup_local']}"
|
||||||
bash_file.write(bash_script)
|
puts " Duración: #{resultado['duracion']}s"
|
||||||
bash_file.chmod(0755)
|
|
||||||
bash_file.close
|
|
||||||
|
|
||||||
remote_bash_path = "/tmp/dron_download_#{Time.now.to_i}.sh"
|
File.write('/tmp/dron_download_output.json', resultado.to_json)
|
||||||
|
puts " JSON guardado en: /tmp/dron_download_output.json"
|
||||||
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
|
|
||||||
|
|||||||
@@ -5,13 +5,14 @@
|
|||||||
# adn/tools/cli/drones/dasuten_exportar_diferencial_srvv-fenix.rb
|
# adn/tools/cli/drones/dasuten_exportar_diferencial_srvv-fenix.rb
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# Dron: Exportar backup DIFERENCIAL de DASUTEN en srvv-fenix
|
# Dron: Exportar backup DIFERENCIAL de DASUTEN en srvv-fenix
|
||||||
# Flujo: srvv-fenix → Backup diferencial en E:\BK_SQL\
|
# Flujo: srvv-fenix → Backup diferencial en X:\ (share srv-ns8)
|
||||||
# Salida: Marca evento como 'export_complete' con ruta del backup
|
# Salida: export_differential_complete con ruta del backup
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
require 'tiny_tds'
|
require 'tiny_tds'
|
||||||
require 'time'
|
require 'time'
|
||||||
require 'json'
|
require 'json'
|
||||||
|
require_relative 'lib/dasu_executor'
|
||||||
|
|
||||||
# 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')
|
||||||
@@ -23,10 +24,10 @@ puts " Destino: #{BACKUP_FILE}"
|
|||||||
puts " Tipo: DIFFERENTIAL (solo cambios desde último backup completo)"
|
puts " Tipo: DIFFERENTIAL (solo cambios desde último backup completo)"
|
||||||
|
|
||||||
begin
|
begin
|
||||||
# Autorizar candados primero (necesario para acceder a la bóveda)
|
# Autorizar candados primero
|
||||||
system("ruby adn/tools/candados/candados.rb authorize >/dev/null 2>&1")
|
system("ruby adn/tools/candados/candados.rb authorize >/dev/null 2>&1")
|
||||||
|
|
||||||
# Obtener credencial de candados (patrón usado en otros drones)
|
# Obtener credencial de candados
|
||||||
pass_cmd = "ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null"
|
pass_cmd = "ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null"
|
||||||
sql_pass = `#{pass_cmd}`.strip
|
sql_pass = `#{pass_cmd}`.strip
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ begin
|
|||||||
sql_host = '10.0.10.200'
|
sql_host = '10.0.10.200'
|
||||||
|
|
||||||
if sql_pass.empty?
|
if sql_pass.empty?
|
||||||
puts "[!] Error: No se pudo obtener credencial de candados (utnlarioja.intranet:monlaricardo)"
|
puts "[!] Error: No se pudo obtener credencial de candados"
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -64,22 +65,14 @@ begin
|
|||||||
duracion = (Time.now - start).round(2)
|
duracion = (Time.now - start).round(2)
|
||||||
puts "[+] Backup diferencial generado en #{duracion} segundos"
|
puts "[+] Backup diferencial generado en #{duracion} segundos"
|
||||||
|
|
||||||
# Verificar archivo en share
|
# Verificar archivo en share usando módulo común
|
||||||
puts "\n[1/2] Verificando archivo en share..."
|
puts "\n[1/2] Verificando archivo en share..."
|
||||||
begin
|
file_info = DasuExecutor.get_file_info_on_fenix(client, BACKUP_FILE)
|
||||||
verify_ps = <<~PSCMD.strip
|
|
||||||
if (Test-Path "#{BACKUP_FILE}") {
|
if file_info[:exists]
|
||||||
$size = (Get-Item "#{BACKUP_FILE}").Length
|
puts "[+] Archivo verificado: #{BACKUP_FILE} (#{file_info[:size_mb]} MB)"
|
||||||
Write-Host "Verificado: #{BACKUP_FILE} ($([math]::Round($size/1MB, 2)) MB)"
|
else
|
||||||
} else {
|
puts "[!] Error: No existe el archivo en #{BACKUP_FILE}"
|
||||||
Write-Host "ERROR: No existe #{BACKUP_FILE}"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
PSCMD
|
|
||||||
client.execute("EXEC xp_cmdshell 'powershell -Command \"#{verify_ps.gsub('"', '\"')}\"'")
|
|
||||||
puts "[+] Archivo verificado"
|
|
||||||
rescue => e
|
|
||||||
puts "[!] Error al verificar en X: - #{e.message}"
|
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,19 +5,18 @@
|
|||||||
# adn/tools/cli/drones/dasuten_exportar_srvv-fenix.rb
|
# adn/tools/cli/drones/dasuten_exportar_srvv-fenix.rb
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# Dron: Exportar backup en srvv-fenix
|
# Dron: Exportar backup en srvv-fenix
|
||||||
# Flujo: srvv-fenix → Backup comprimido en E:\BK_SQL\
|
# Flujo: srvv-fenix → Backup comprimido en X:\ (share srv-ns8)
|
||||||
# Salida: Marca evento como 'export_complete' con ruta del backup
|
# Salida: export_complete con ruta del backup
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
||||||
require 'tiny_tds'
|
require 'tiny_tds'
|
||||||
require 'time'
|
require 'time'
|
||||||
require 'json'
|
require 'json'
|
||||||
|
require_relative 'lib/dasu_executor'
|
||||||
|
|
||||||
# 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_FILE = "sysdasuten_FULL_#{TIMESTAMP}.bak"
|
BACKUP_FILE = "sysdasuten_FULL_#{TIMESTAMP}.bak"
|
||||||
|
|
||||||
# Unidad de red mapeada en srvv-fenix (share de srv-ns8) - UNICO DESTINO
|
|
||||||
BACKUP_NS8 = "X:\\#{BACKUP_FILE}"
|
BACKUP_NS8 = "X:\\#{BACKUP_FILE}"
|
||||||
|
|
||||||
puts "[*] Dron: Exportar backup COMPLETO DASUTEN"
|
puts "[*] Dron: Exportar backup COMPLETO DASUTEN"
|
||||||
@@ -26,10 +25,10 @@ puts " Destino: #{BACKUP_NS8}"
|
|||||||
puts " Tipo: FULL (completo)"
|
puts " Tipo: FULL (completo)"
|
||||||
|
|
||||||
begin
|
begin
|
||||||
# Autorizar candados primero (necesario para acceder a la bóveda)
|
# Autorizar candados primero
|
||||||
system("ruby adn/tools/candados/candados.rb authorize >/dev/null 2>&1")
|
system("ruby adn/tools/candados/candados.rb authorize >/dev/null 2>&1")
|
||||||
|
|
||||||
# Obtener credencial de candados (patrón usado en otros drones)
|
# Obtener credencial de candados
|
||||||
pass_cmd = "ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null"
|
pass_cmd = "ruby adn/tools/candados/candados.rb get utnlarioja.intranet:monlaricardo 2>/dev/null"
|
||||||
sql_pass = `#{pass_cmd}`.strip
|
sql_pass = `#{pass_cmd}`.strip
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ begin
|
|||||||
sql_host = '10.0.10.200'
|
sql_host = '10.0.10.200'
|
||||||
|
|
||||||
if sql_pass.empty?
|
if sql_pass.empty?
|
||||||
puts "[!] Error: No se pudo obtener credencial de candados (utnlarioja.intranet:monlaricardo)"
|
puts "[!] Error: No se pudo obtener credencial de candados"
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -51,8 +50,7 @@ begin
|
|||||||
|
|
||||||
# Crear directorio en share si no existe
|
# Crear directorio en share si no existe
|
||||||
puts "\n[0/2] Verificando directorio en share..."
|
puts "\n[0/2] Verificando directorio en share..."
|
||||||
mkdir_sql = "EXEC master.sys.xp_create_subdir 'X:\\'"
|
client.execute("EXEC master.sys.xp_create_subdir 'X:\\'")
|
||||||
client.execute(mkdir_sql)
|
|
||||||
puts "[+] Directorio share asegurado: X:\\"
|
puts "[+] Directorio share asegurado: X:\\"
|
||||||
|
|
||||||
# Generar backup comprimido directamente en share X:
|
# Generar backup comprimido directamente en share X:
|
||||||
@@ -65,21 +63,14 @@ begin
|
|||||||
duracion = (Time.now - start).round(2)
|
duracion = (Time.now - start).round(2)
|
||||||
puts "[+] Backup generado en #{duracion} segundos"
|
puts "[+] Backup generado en #{duracion} segundos"
|
||||||
|
|
||||||
# Verificar archivo en share
|
# Verificar archivo en share usando módulo común
|
||||||
puts "[2/2] Verificando archivo en share..."
|
puts "[2/2] Verificando archivo en share..."
|
||||||
begin
|
file_info = DasuExecutor.get_file_info_on_fenix(client, BACKUP_NS8)
|
||||||
verify_ps = <<~PSCMD.strip
|
|
||||||
if (Test-Path "#{BACKUP_NS8}") {
|
if file_info[:exists]
|
||||||
$size = (Get-Item "#{BACKUP_NS8}").Length
|
puts "[+] Archivo verificado: #{BACKUP_NS8} (#{file_info[:size_mb]} MB)"
|
||||||
Write-Host "Verificado: #{BACKUP_NS8} ($([math]::Round($size/1MB, 2)) MB)"
|
else
|
||||||
} else {
|
puts "[!] Error: No existe el archivo en #{BACKUP_NS8}"
|
||||||
Write-Host "ERROR: No existe #{BACKUP_NS8}"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
PSCMD
|
|
||||||
client.execute("EXEC xp_cmdshell 'powershell -Command \"#{verify_ps.gsub('"', '\"')}\"'")
|
|
||||||
rescue => e
|
|
||||||
puts "[!] Error al verificar en X: - #{e.message}"
|
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -101,7 +92,6 @@ begin
|
|||||||
puts "\n✅ Export completado en share"
|
puts "\n✅ Export completado en share"
|
||||||
puts " Output: #{output.to_json}"
|
puts " Output: #{output.to_json}"
|
||||||
|
|
||||||
# Escribir output para que el orquestador lo lea
|
|
||||||
File.write('/tmp/dron_export_output.json', output.to_json)
|
File.write('/tmp/dron_export_output.json', output.to_json)
|
||||||
|
|
||||||
rescue => e
|
rescue => e
|
||||||
|
|||||||
@@ -12,18 +12,16 @@
|
|||||||
|
|
||||||
require 'time'
|
require 'time'
|
||||||
require 'json'
|
require 'json'
|
||||||
require 'base64'
|
require_relative 'lib/dasu_executor'
|
||||||
require 'tempfile'
|
|
||||||
require 'shellwords'
|
|
||||||
|
|
||||||
DOWNLOAD_OUTPUT = '/tmp/dron_download_output.json'
|
DOWNLOAD_OUTPUT = '/tmp/dron_download_output.json'
|
||||||
BACKUP_DIR_WINDOWS = 'F:\\BACKUP'
|
BACKUP_DIR_WINDOWS = 'F:\\BACKUP'
|
||||||
|
JSON_PATH = 'C:\temp\dron_restore_output.json'
|
||||||
|
|
||||||
# Leer ruta del backup desde el output del download
|
# Leer ruta del backup desde el output del download
|
||||||
if File.exist?(DOWNLOAD_OUTPUT)
|
if File.exist?(DOWNLOAD_OUTPUT)
|
||||||
output = JSON.parse(File.read(DOWNLOAD_OUTPUT))
|
output = JSON.parse(File.read(DOWNLOAD_OUTPUT))
|
||||||
backup_local = output['backup_local']
|
backup_local = output['backup_local']
|
||||||
# Convertir path Unix a Windows si es necesario
|
|
||||||
backup_name = File.basename(backup_local)
|
backup_name = File.basename(backup_local)
|
||||||
LOCAL_BACKUP = "#{BACKUP_DIR_WINDOWS}\\#{backup_name}"
|
LOCAL_BACKUP = "#{BACKUP_DIR_WINDOWS}\\#{backup_name}"
|
||||||
puts "[*] Dron: Restaurar backup DASUTEN"
|
puts "[*] Dron: Restaurar backup DASUTEN"
|
||||||
@@ -32,24 +30,18 @@ else
|
|||||||
puts "[*] Dron: Restaurar backup DASUTEN (buscar más reciente)"
|
puts "[*] Dron: Restaurar backup DASUTEN (buscar más reciente)"
|
||||||
end
|
end
|
||||||
|
|
||||||
TARGET_IP = "192.168.1.11"
|
|
||||||
TARGET_USER = "Administrador"
|
|
||||||
TARGET_PASS = "UTNlarioja00DASU"
|
|
||||||
|
|
||||||
puts " Nodo: dasu-sql4"
|
puts " Nodo: dasu-sql4"
|
||||||
puts " Backup: #{LOCAL_BACKUP}"
|
puts " Backup: #{LOCAL_BACKUP}"
|
||||||
|
|
||||||
# PowerShell script para restore (sin checkdb)
|
# PowerShell script para restore
|
||||||
ps_script = <<~'PSCMD'
|
ps_script = <<~'PSCMD'.strip
|
||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
$BACKUP_DIR = "F:\BACKUP"
|
$BACKUP_DIR = "F:\BACKUP"
|
||||||
$BACKUP_PATTERN = "sysdasuten_FULL_*.bak"
|
$BACKUP_PATTERN = "sysdasuten_FULL_*.bak"
|
||||||
$JSON_PATH = "C:\temp\dron_restore_output.json"
|
$JSON_PATH = "C:\temp\dron_restore_output.json"
|
||||||
|
|
||||||
# Asegurar directorio temp
|
|
||||||
$null = New-Item -ItemType Directory -Force -Path "C:\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..."
|
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
|
$backupFile = Get-ChildItem "$BACKUP_DIR\$BACKUP_PATTERN" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||||
if (-not $backupFile) {
|
if (-not $backupFile) {
|
||||||
@@ -63,10 +55,8 @@ ps_script = <<~'PSCMD'
|
|||||||
Write-Host "[1/1] Ejecutando RESTORE DATABASE..."
|
Write-Host "[1/1] Ejecutando RESTORE DATABASE..."
|
||||||
$start = Get-Date
|
$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"
|
$sqlcmd = "C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\sqlcmd.exe"
|
||||||
|
|
||||||
# Comando RESTORE
|
|
||||||
$restoreSql = @"
|
$restoreSql = @"
|
||||||
RESTORE DATABASE sysdasuten
|
RESTORE DATABASE sysdasuten
|
||||||
FROM DISK = '$LOCAL_BACKUP'
|
FROM DISK = '$LOCAL_BACKUP'
|
||||||
@@ -77,7 +67,6 @@ ps_script = <<~'PSCMD'
|
|||||||
|
|
||||||
Write-Host " SQL: $restoreSql"
|
Write-Host " SQL: $restoreSql"
|
||||||
|
|
||||||
# Ejecutar restore
|
|
||||||
$restoreOutput = & $sqlcmd -S localhost -Q $restoreSql 2>&1
|
$restoreOutput = & $sqlcmd -S localhost -Q $restoreSql 2>&1
|
||||||
$exitoRestore = $LASTEXITCODE -eq 0
|
$exitoRestore = $LASTEXITCODE -eq 0
|
||||||
$duracionRestore = [math]::Round((New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds, 2)
|
$duracionRestore = [math]::Round((New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds, 2)
|
||||||
@@ -90,14 +79,11 @@ ps_script = <<~'PSCMD'
|
|||||||
|
|
||||||
Write-Host "[+] Restore completado en $duracionRestore segundos"
|
Write-Host "[+] Restore completado en $duracionRestore segundos"
|
||||||
|
|
||||||
$duracionTotal = $duracionRestore
|
|
||||||
|
|
||||||
# Output JSON
|
|
||||||
$res = @{
|
$res = @{
|
||||||
paso = "restore_complete"
|
paso = "restore_complete"
|
||||||
backup_ruta = $LOCAL_BACKUP
|
backup_ruta = $LOCAL_BACKUP
|
||||||
nodo = "dasu-sql4"
|
nodo = "dasu-sql4"
|
||||||
duracion_total = $duracionTotal
|
duracion_total = $duracionRestore
|
||||||
duracion_restore = $duracionRestore
|
duracion_restore = $duracionRestore
|
||||||
estado_integridad = "PENDING_CHECKDB"
|
estado_integridad = "PENDING_CHECKDB"
|
||||||
timestamp = (Get-Date -Format "o")
|
timestamp = (Get-Date -Format "o")
|
||||||
@@ -110,90 +96,18 @@ ps_script = <<~'PSCMD'
|
|||||||
$res | ConvertTo-Json | Out-File -FilePath $JSON_PATH -Encoding utf8
|
$res | ConvertTo-Json | Out-File -FilePath $JSON_PATH -Encoding utf8
|
||||||
PSCMD
|
PSCMD
|
||||||
|
|
||||||
# Codificar script en base64
|
puts "\n[1/2] Ejecutando restore en dasu-sql4..."
|
||||||
ps_encoded = Base64.strict_encode64(ps_script)
|
DasuExecutor.execute_ps_on_dasu(ps_script, output_path: JSON_PATH)
|
||||||
|
|
||||||
# Script bash que se ejecuta en srv-dasu
|
puts "\n[2/2] Leyendo output JSON..."
|
||||||
bash_script = <<~BASHHEREDOC
|
resultado = DasuExecutor.read_json_from_dasu(JSON_PATH)
|
||||||
#!/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
|
puts " Output: #{resultado.to_json}"
|
||||||
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✅ Restauración completada"
|
||||||
|
puts " Backup: #{resultado['backup_ruta']}"
|
||||||
|
puts " Estado: #{resultado['estado_integridad']}"
|
||||||
|
puts " Duración: #{resultado['duracion_total']}s"
|
||||||
|
|
||||||
puts "\n[1/3] Copiando script a srv-dasu..."
|
File.write('/tmp/dron_restore_output.json', resultado.to_json)
|
||||||
scp_cmd = "scp -o StrictHostKeyChecking=no #{bash_file.path} srv-dasu:#{remote_bash_path}"
|
puts " JSON guardado en: /tmp/dron_restore_output.json"
|
||||||
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
|
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# adn/tools/cli/drones/dasuten_restaurar_diferencial_dasu-sql4.rb
|
||||||
|
# ==========================================================
|
||||||
|
# Dron: Restaurar backup DIFERENCIAL en dasu-sql4
|
||||||
|
# Flujo: RESTORE DATABASE ... WITH DIFFERENTIAL (sobre BD existente)
|
||||||
|
# Entrada: Backup diferencial en F:\BACKUP\
|
||||||
|
# Salida: differential_restore_complete
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
require 'time'
|
||||||
|
require 'json'
|
||||||
|
require_relative 'lib/dasu_executor'
|
||||||
|
|
||||||
|
JSON_PATH = 'C:\temp\dron_restore_dif_output.json'
|
||||||
|
|
||||||
|
puts "[*] Dron: Restaurar backup DIFERENCIAL DASUTEN"
|
||||||
|
puts " Nodo: dasu-sql4"
|
||||||
|
puts " Backup: sysdasuten_DIF_*.bak (más reciente)"
|
||||||
|
puts " Tipo: DIFFERENTIAL (aplica cambios sobre BD existente)"
|
||||||
|
|
||||||
|
# PowerShell script para restore diferencial
|
||||||
|
ps_script = <<~'PSCMD'.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$JSON_PATH = "C:\temp\dron_restore_dif_output.json"
|
||||||
|
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path "C:\temp"
|
||||||
|
|
||||||
|
Write-Host "[1/3] Buscando backup diferencial más reciente..."
|
||||||
|
$backupFile = Get-ChildItem "F:\BACKUP\sysdasuten_DIF_*.bak" | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||||
|
|
||||||
|
if (-not $backupFile) {
|
||||||
|
Write-Host "[!] No se encontró backup diferencial en F:\BACKUP\"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$LOCAL_BACKUP = $backupFile.FullName
|
||||||
|
Write-Host " Archivo: $LOCAL_BACKUP"
|
||||||
|
Write-Host " Tamaño: $([math]::Round($backupFile.Length / 1MB, 2)) MB"
|
||||||
|
|
||||||
|
$sqlcmd = "C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\sqlcmd.exe"
|
||||||
|
|
||||||
|
Write-Host "`n[2/3] Ejecutando RESTORE DATABASE ... WITH DIFFERENTIAL..."
|
||||||
|
$start = Get-Date
|
||||||
|
|
||||||
|
$restoreSql = @"
|
||||||
|
RESTORE DATABASE sysdasuten
|
||||||
|
FROM DISK = '$LOCAL_BACKUP'
|
||||||
|
WITH NORECOVERY, REPLACE
|
||||||
|
"@
|
||||||
|
|
||||||
|
Write-Host " SQL: $restoreSql"
|
||||||
|
|
||||||
|
$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 diferencial completado en $duracionRestore segundos"
|
||||||
|
|
||||||
|
Write-Host "`n[3/3] Poniendo base de datos ONLINE..."
|
||||||
|
|
||||||
|
$recoverySql = "RESTORE DATABASE sysdasuten WITH RECOVERY"
|
||||||
|
$recoveryOutput = & $sqlcmd -S localhost -Q $recoverySql 2>&1
|
||||||
|
$exitoRecovery = $LASTEXITCODE -eq 0
|
||||||
|
$duracionRecovery = [math]::Round((New-TimeSpan -Start (Get-Date) -End (Get-Date)).TotalSeconds, 2)
|
||||||
|
|
||||||
|
if ($exitoRecovery) {
|
||||||
|
Write-Host "[+] Base de datos ONLINE"
|
||||||
|
} else {
|
||||||
|
Write-Host "[!] Error en recovery:"
|
||||||
|
Write-Host $recoveryOutput
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$duracionTotal = [math]::Round($duracionRestore + $duracionRecovery, 2)
|
||||||
|
|
||||||
|
$res = @{
|
||||||
|
paso = "differential_restore_complete"
|
||||||
|
backup_ruta = $LOCAL_BACKUP
|
||||||
|
backup_tipo = "differential"
|
||||||
|
nodo = "dasu-sql4"
|
||||||
|
duracion_total = $duracionTotal
|
||||||
|
duracion_restore = $duracionRestore
|
||||||
|
duracion_recovery = $duracionRecovery
|
||||||
|
estado_integridad = "PENDING_CHECKDB"
|
||||||
|
timestamp = (Get-Date -Format "o")
|
||||||
|
siguiente_paso = "verificar_integridad"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n✅ Restauración diferencial completada"
|
||||||
|
Write-Host " Estado: BD ONLINE"
|
||||||
|
Write-Host " Output: $($res | ConvertTo-Json -Compress)"
|
||||||
|
|
||||||
|
$res | ConvertTo-Json | Out-File -FilePath $JSON_PATH -Encoding utf8
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
puts "\n[1/2] Ejecutando restore diferencial en dasu-sql4..."
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script, output_path: JSON_PATH)
|
||||||
|
|
||||||
|
puts "\n[2/2] Leyendo output JSON..."
|
||||||
|
resultado = DasuExecutor.read_json_from_dasu(JSON_PATH)
|
||||||
|
|
||||||
|
puts " Output: #{resultado.to_json}"
|
||||||
|
|
||||||
|
puts "\n✅ Restauración diferencial completada"
|
||||||
|
puts " Backup: #{resultado['backup_ruta']}"
|
||||||
|
puts " Estado: #{resultado['estado_integridad']}"
|
||||||
|
puts " Duración total: #{resultado['duracion_total']}s"
|
||||||
|
|
||||||
|
File.write('/tmp/dron_restore_dif_output.json', resultado.to_json)
|
||||||
|
puts " JSON guardado en: /tmp/dron_restore_dif_output.json"
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# adn/tools/cli/drones/dasuten_verificar-integridad_dasu-sql4.rb
|
||||||
|
# ==========================================================
|
||||||
|
# Dron: Verificar integridad de BD en dasu-sql4
|
||||||
|
# Flujo: DBCC CHECKDB + validaciones post-restore
|
||||||
|
# Entrada: BD restaurada (pendiente verificación)
|
||||||
|
# Salida: verify_complete con estado de integridad
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
require 'time'
|
||||||
|
require 'json'
|
||||||
|
require_relative 'lib/dasu_executor'
|
||||||
|
|
||||||
|
JSON_PATH = 'C:\temp\dron_verify_output.json'
|
||||||
|
|
||||||
|
puts "[*] Dron: Verificar integridad BD DASUTEN"
|
||||||
|
puts " Nodo: dasu-sql4"
|
||||||
|
|
||||||
|
# PowerShell script para DBCC CHECKDB
|
||||||
|
ps_script = <<~'PSCMD'.strip
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$JSON_PATH = "C:\temp\dron_verify_output.json"
|
||||||
|
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path "C:\temp"
|
||||||
|
|
||||||
|
Write-Host "[1/2] Ejecutando DBCC CHECKDB..."
|
||||||
|
$start = Get-Date
|
||||||
|
|
||||||
|
$sqlcmd = "C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\sqlcmd.exe"
|
||||||
|
|
||||||
|
$checkdbSql = "DBCC CHECKDB('sysdasuten') WITH NO_INFOMSGS, ALL_ERRORMSGS"
|
||||||
|
Write-Host " SQL: $checkdbSql"
|
||||||
|
|
||||||
|
$checkdbOutput = & $sqlcmd -S localhost -Q $checkdbSql 2>&1
|
||||||
|
$exitoCheckdb = $LASTEXITCODE -eq 0
|
||||||
|
$duracionCheckdb = [math]::Round((New-TimeSpan -Start $start -End (Get-Date)).TotalSeconds, 2)
|
||||||
|
|
||||||
|
if ($exitoCheckdb) {
|
||||||
|
Write-Host "[+] CHECKDB completado en $duracionCheckdb segundos"
|
||||||
|
|
||||||
|
$tieneErrores = $checkdbOutput -match 'ERROR|corrupt|damaged|inconsistency'
|
||||||
|
$estadoIntegridad = if ($tieneErrores) { 'ERROR_DETECTED' } else { 'OK' }
|
||||||
|
|
||||||
|
$paginasVerificadas = 0
|
||||||
|
if ($checkdbOutput -match '(\d+)\s+pages') {
|
||||||
|
$paginasVerificadas = [int]$matches[1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$estadoIntegridad = 'CHECKDB_FAILED'
|
||||||
|
Write-Host "[!] CHECKDB falló:"
|
||||||
|
Write-Host $checkdbOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n[2/2] Validaciones adicionales..."
|
||||||
|
|
||||||
|
$bdStatusSql = "SELECT state_desc, recovery_model_desc FROM sys.databases WHERE name = 'sysdasuten'"
|
||||||
|
$bdStatus = & $sqlcmd -S localhost -Q $bdStatusSql -h -1
|
||||||
|
|
||||||
|
Write-Host " Estado BD: $bdStatus"
|
||||||
|
|
||||||
|
$res = @{
|
||||||
|
paso = "verify_complete"
|
||||||
|
nodo = "dasu-sql4"
|
||||||
|
duracion_checkdb = $duracionCheckdb
|
||||||
|
estado_integridad = $estadoIntegridad
|
||||||
|
paginas_verificadas = $paginasVerificadas
|
||||||
|
bd_estado = $bdStatus
|
||||||
|
timestamp = (Get-Date -Format "o")
|
||||||
|
pipeline_completo = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n✅ Verificación completada"
|
||||||
|
Write-Host " Estado integridad: $estadoIntegridad"
|
||||||
|
Write-Host " Output: $($res | ConvertTo-Json -Compress)"
|
||||||
|
|
||||||
|
$res | ConvertTo-Json | Out-File -FilePath $JSON_PATH -Encoding utf8
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
puts "\n[1/2] Ejecutando DBCC CHECKDB en dasu-sql4..."
|
||||||
|
DasuExecutor.execute_ps_on_dasu(ps_script, output_path: JSON_PATH)
|
||||||
|
|
||||||
|
puts "\n[2/2] Leyendo output JSON..."
|
||||||
|
resultado = DasuExecutor.read_json_from_dasu(JSON_PATH)
|
||||||
|
|
||||||
|
puts " Output: #{resultado.to_json}"
|
||||||
|
|
||||||
|
puts "\n✅ Verificación completada"
|
||||||
|
puts " Estado integridad: #{resultado['estado_integridad']}"
|
||||||
|
puts " Páginas verificadas: #{resultado['paginas_verificadas'] || 'N/A'}"
|
||||||
|
puts " Duración: #{resultado['duracion_checkdb']}s"
|
||||||
|
|
||||||
|
File.write('/tmp/dron_verify_output.json', resultado.to_json)
|
||||||
|
puts " JSON guardado en: /tmp/dron_verify_output.json"
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# ==========================================================
|
||||||
|
# adn/tools/cli/drones/lib/dasu_executor.rb
|
||||||
|
# ==========================================================
|
||||||
|
# Módulo helper para ejecutar PowerShell en dasu-sql4 vía srv-dasu
|
||||||
|
# Centraliza: SSH, sshpass, base64 encoding, lectura de JSON
|
||||||
|
# ==========================================================
|
||||||
|
|
||||||
|
require 'base64'
|
||||||
|
require 'tempfile'
|
||||||
|
|
||||||
|
module DasuExecutor
|
||||||
|
# Configuración del target dasu-sql4
|
||||||
|
TARGET_IP = '192.168.1.11'
|
||||||
|
TARGET_USER = 'Administrador'
|
||||||
|
TARGET_PASS = 'UTNlarioja00DASU'
|
||||||
|
TARGET_PORT = '7022'
|
||||||
|
|
||||||
|
# Path donde se guardan los JSON output en dasu-sql4
|
||||||
|
DEFAULT_JSON_PATH = 'C:\temp\output.json'
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
# Ejecuta un script PowerShell en dasu-sql4 vía srv-dasu
|
||||||
|
#
|
||||||
|
# @param ps_script [String] Script PowerShell a ejecutar
|
||||||
|
# @param output_path [String] Path donde PowerShell guardará el JSON (default: C:\temp\output.json)
|
||||||
|
# @return [Hash] JSON parseado del output
|
||||||
|
#
|
||||||
|
# Ejemplo:
|
||||||
|
# result = DasuExecutor.execute_ps_on_dasu(<<~PS)
|
||||||
|
# @{ status = "ok" } | ConvertTo-Json | Out-File C:\temp\out.json
|
||||||
|
# Get-Content C:\temp\out.json -Raw
|
||||||
|
# PS
|
||||||
|
def execute_ps_on_dasu(ps_script, output_path: DEFAULT_JSON_PATH)
|
||||||
|
ps_encoded = Base64.strict_encode64(ps_script)
|
||||||
|
|
||||||
|
bash_script = <<~BASHHEREDOC
|
||||||
|
#!/bin/bash
|
||||||
|
export SSHPASS="#{TARGET_PASS}"
|
||||||
|
sshpass -e ssh -o StrictHostKeyChecking=no -p #{TARGET_PORT} #{TARGET_USER}@#{TARGET_IP} \
|
||||||
|
"powershell -Command \\\"Invoke-Expression ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('#{ps_encoded}')))\\\""
|
||||||
|
BASHHEREDOC
|
||||||
|
|
||||||
|
execute_bash_temporary(bash_script)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ejecuta un script PowerShell y lee el JSON resultante
|
||||||
|
# Asume que el script escribe un JSON en output_path
|
||||||
|
#
|
||||||
|
# @param ps_script [String] Script PowerShell a ejecutar
|
||||||
|
# @param output_path [String] Path del JSON en dasu-sql4
|
||||||
|
# @return [Hash] JSON parseado
|
||||||
|
def execute_ps_with_json_output(ps_script, output_path: DEFAULT_JSON_PATH)
|
||||||
|
# Ejecutar el script principal
|
||||||
|
execute_ps_on_dasu(ps_script, output_path: output_path)
|
||||||
|
|
||||||
|
# Leer el JSON resultante
|
||||||
|
read_json_from_dasu(output_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Lee un JSON desde dasu-sql4
|
||||||
|
#
|
||||||
|
# @param output_path [String] Path del JSON en dasu-sql4 (ej: C:\temp\output.json)
|
||||||
|
# @return [Hash] JSON parseado
|
||||||
|
def read_json_from_dasu(output_path)
|
||||||
|
json_ps = "Get-Content #{output_path} -Raw"
|
||||||
|
json_encoded = Base64.strict_encode64(json_ps)
|
||||||
|
|
||||||
|
json_script = <<~BASHHEREDOC.strip
|
||||||
|
#!/bin/bash
|
||||||
|
export SSHPASS="#{TARGET_PASS}"
|
||||||
|
sshpass -e ssh -o StrictHostKeyChecking=no -p #{TARGET_PORT} #{TARGET_USER}@#{TARGET_IP} \
|
||||||
|
"powershell -Command \\\"Invoke-Expression ([System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('#{json_encoded}')))\\\""
|
||||||
|
BASHHEREDOC
|
||||||
|
|
||||||
|
resultado = execute_bash_temporary(json_script)
|
||||||
|
resultado = resultado.strip
|
||||||
|
|
||||||
|
# Extraer JSON del output (puede tener ruido)
|
||||||
|
json_match = resultado.match(/\{.*\}/m)
|
||||||
|
resultado = json_match ? json_match[0] : resultado
|
||||||
|
|
||||||
|
require 'json'
|
||||||
|
JSON.parse(resultado)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ejecuta un script bash temporal y lo limpia después
|
||||||
|
#
|
||||||
|
# @param bash_script [String] Script bash a ejecutar
|
||||||
|
# @param remote_base [String] Directorio remoto base (default: /tmp)
|
||||||
|
# @return [String] Output del comando
|
||||||
|
def execute_bash_temporary(bash_script, remote_base: '/tmp')
|
||||||
|
bash_file = Tempfile.new(['dasu', '.sh'])
|
||||||
|
bash_file.write(bash_script)
|
||||||
|
bash_file.chmod(0755)
|
||||||
|
bash_file.close
|
||||||
|
|
||||||
|
remote_path = "#{remote_base}/dron_#{Time.now.to_i}.sh"
|
||||||
|
|
||||||
|
# Copiar script a srv-dasu
|
||||||
|
scp_cmd = "scp -o StrictHostKeyChecking=no #{bash_file.path} srv-dasu:#{remote_path}"
|
||||||
|
unless system(scp_cmd)
|
||||||
|
bash_file.unlink
|
||||||
|
raise "Error al copiar script a srv-dasu"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ejecutar script
|
||||||
|
ssh_cmd = "ssh srv-dasu 'bash #{remote_path}'"
|
||||||
|
output = `#{ssh_cmd}`
|
||||||
|
output = output.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') if output
|
||||||
|
|
||||||
|
# Limpieza
|
||||||
|
bash_file.unlink
|
||||||
|
system("ssh srv-dasu 'rm -f #{remote_path}' 2>/dev/null")
|
||||||
|
|
||||||
|
output
|
||||||
|
end
|
||||||
|
|
||||||
|
# Helper para ejecutar PowerShell en fenix vía xp_cmdshell
|
||||||
|
# Útil para verificar archivos en shares mapeados
|
||||||
|
#
|
||||||
|
# @param client [TinyTds::Client] Conexión SQL Server
|
||||||
|
# @param ps_script [String] Script PowerShell a ejecutar
|
||||||
|
# @return [Array] Resultado de la ejecución
|
||||||
|
def execute_ps_on_fenix(client, ps_script)
|
||||||
|
escaped = ps_script.gsub('"', '\"')
|
||||||
|
client.execute("EXEC xp_cmdshell 'powershell -Command \"#{escaped}\"'")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Verifica si un archivo existe en un path Windows/fenix
|
||||||
|
#
|
||||||
|
# @param client [TinyTds::Client] Conexión SQL Server
|
||||||
|
# @param path [String] Path del archivo (ej: X:\backup.bak)
|
||||||
|
# @return [Boolean] true si existe
|
||||||
|
def file_exists_on_fenix?(client, path)
|
||||||
|
result = client.execute("EXEC master.sys.xp_fileexist '#{path}'")
|
||||||
|
result.to_a.any? { |f| f['File Exists'] == 1 }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Obtiene información de tamaño de un archivo en fenix
|
||||||
|
#
|
||||||
|
# @param client [TinyTds::Client] Conexión SQL Server
|
||||||
|
# @param path [String] Path del archivo
|
||||||
|
# @return [Hash] Con :exists, :size_bytes, :size_mb
|
||||||
|
def get_file_info_on_fenix(client, path)
|
||||||
|
ps_script = <<~PSCMD.strip
|
||||||
|
if (Test-Path "#{path}") {
|
||||||
|
$item = Get-Item "#{path}"
|
||||||
|
Write-Host "EXISTS:1"
|
||||||
|
Write-Host "SIZE:$($item.Length)"
|
||||||
|
} else {
|
||||||
|
Write-Host "EXISTS:0"
|
||||||
|
}
|
||||||
|
PSCMD
|
||||||
|
|
||||||
|
result = execute_ps_on_fenix(client, ps_script)
|
||||||
|
output = result.map { |r| r['output'] }.compact.join("\n")
|
||||||
|
|
||||||
|
exists = output.include?('EXISTS:1')
|
||||||
|
size = output.match(/SIZE:(\d+)/) ? $1.to_i : 0
|
||||||
|
|
||||||
|
{
|
||||||
|
exists: exists,
|
||||||
|
size_bytes: size,
|
||||||
|
size_mb: (size / 1024.0 / 1024.0).round(2)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user