chore(dasuten): Actualizaciones migración SQL, W-Zombi y scripts
This commit is contained in:
@@ -25,6 +25,9 @@ gem "libnotify", "~> 0.9", :platforms => :ruby
|
||||
# Conexión a PostgreSQL para dtic-BITACORAs
|
||||
gem "pg", "~> 1.5"
|
||||
|
||||
# Conexión a MS SQL Server nativa (freetds-dev requerido)
|
||||
gem "tiny_tds", "~> 2.1.7"
|
||||
|
||||
# ─── Gemas de desarrollo y pruebas ────────────────────────────────────
|
||||
group :development, :test do
|
||||
# Framework de pruebas
|
||||
|
||||
@@ -101,6 +101,7 @@ GEM
|
||||
tilt (~> 2.0)
|
||||
tilt (2.7.0)
|
||||
timeout (0.6.0)
|
||||
tiny_tds (2.1.7)
|
||||
uri (1.1.1)
|
||||
webmock (3.26.1)
|
||||
addressable (>= 2.8.0)
|
||||
@@ -129,6 +130,7 @@ DEPENDENCIES
|
||||
safe_yaml (~> 1.0)
|
||||
simplecov (~> 0.22)
|
||||
sinatra (~> 3.0)
|
||||
tiny_tds (~> 2.1.7)
|
||||
webmock (~> 3.19)
|
||||
webrick (~> 1.8)
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
"srv-dasu:root": "eyJpdiI6Ik5LY09JR1FvaFVPRXd3bjUiLCJ0YWciOiJwb0NLZ3A4N3VOQUZMQWhMS1hUVWZnPT0iLCJkYXRhIjoicEgvWTFmd0RhSFQ0YWRHRUxyZ1lBQT09In0=",
|
||||
"dasu-dc:admindasu": "eyJpdiI6IjlYV0JRSVA1MzNLblFycUEiLCJ0YWciOiJ3aHI4a1cwanpLWU5OUmVHby9pd3ZRPT0iLCJkYXRhIjoiT1B3VGNHNkpUSWI0SnJ5NW5adHNVZz09In0=",
|
||||
"srvv-nginx-rm:root": "eyJpdiI6Ijc0STVkTk92Tk5LZEZZWWciLCJ0YWciOiJlOTAwcGpTa21PZFVhN2FJTzIxU1VBPT0iLCJkYXRhIjoiYUJRR3hRakFub1F1OCtyTVdWcW8ifQ==",
|
||||
"dominio-utnlarioja.intranet:monlaricardo": "eyJpdiI6InFRQ3VNYWlVSHlKMURNdzciLCJ0YWciOiJ1a3hESUZlaEt6enV3UXZxWUFrWXhRPT0iLCJkYXRhIjoiNVNaekdxVyt6dE5WQmhPaXF3PT0ifQ==",
|
||||
"utnlarioja.intranet:monlaricardo": "eyJpdiI6InFRQ3VNYWlVSHlKMURNdzciLCJ0YWciOiJ1a3hESUZlaEt6enV3UXZxWUFrWXhRPT0iLCJkYXRhIjoiNVNaekdxVyt6dE5WQmhPaXF3PT0ifQ==",
|
||||
"deepseek_key": "eyJpdiI6InRCc2IvL3J4SGNsejgvL04iLCJ0YWciOiJBbjRlMTNXNlZzUGhNSWowSUU1a01nPT0iLCJkYXRhIjoiQXR4azk1aU9sb2QydzNwa0FMTHpjVkJKQTIvZU4wY1U2V2crK3NBbVd1UnRmUGh6In0=",
|
||||
"srv-ns8:rmonla": "eyJpdiI6IlJ4cHZkbFpJQWVsTTl3T1YiLCJ0YWciOiJ3WTZGMDEvNHNsZm1wZm5jMEF4Q1lBPT0iLCJkYXRhIjoibFljUHRYTm44bUxvQjhCa2tBPT0ifQ==",
|
||||
"dasu-sql3:Administrador": "eyJpdiI6IlRiMDh3U1N2aVhTcDYzbzciLCJ0YWciOiI5NVNyUDFlMXlrM0dGRlNqUXhLbEt3PT0iLCJkYXRhIjoiVkp2V09zSjczcS96UktTSlZTOFdBdz09In0=",
|
||||
"srvv-nginx-rm:rmonla": "eyJpdiI6ImFsQnk5UjhLaDFYWHJsWnciLCJ0YWciOiJJaEtCS215ekkwYVdFR0ZHNzFVN0VBPT0iLCJkYXRhIjoiUkcvSS9sMjhoQnFpRmZobFZBPT0ifQ=="
|
||||
}
|
||||
}
|
||||
Executable
+37
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
require 'tiny_tds'
|
||||
require 'fileutils'
|
||||
|
||||
puts "⏳ Iniciando proceso de backup en origen (srvv-fenix)..."
|
||||
|
||||
begin
|
||||
client = TinyTds::Client.new(
|
||||
username: 'UTNlarioja\\monlaricardo',
|
||||
password: ENV['PASS'],
|
||||
host: '10.0.10.200',
|
||||
timeout: 300 # 5 minutos max
|
||||
)
|
||||
|
||||
puts "✅ Conectado a la instancia MSSQL: 10.0.10.200"
|
||||
|
||||
backup_file = "E:\\BK_SQL\\sysdasuten_compressed_ADN.bak"
|
||||
|
||||
query = <<~SQL
|
||||
BACKUP DATABASE [sysdasuten]
|
||||
TO DISK = N'#{backup_file}'
|
||||
WITH FORMAT, INIT,
|
||||
NAME = N'sysdasuten-Full Database Backup (ADN COMPRESSION)',
|
||||
SKIP, NOREWIND, NOUNLOAD, COMPRESSION, STATS = 10
|
||||
SQL
|
||||
|
||||
puts "⏳ Ejecutando BACKUP DATABASE con COMPRESSION..."
|
||||
result = client.execute(query)
|
||||
result.do
|
||||
|
||||
puts "✅ Backup completado en origen: #{backup_file}"
|
||||
|
||||
rescue => e
|
||||
puts "❌ Error crítico fallido: #{e.message}"
|
||||
exit 1
|
||||
end
|
||||
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Transferencia y restauración de DASUTEN backup comprimido a dasu-sql4
|
||||
# Ruta: srvv-fenix (SMB) → NS8 (local) → srv-dasu (SCP) → dasu-sql4 (HTTP → RESTORE)
|
||||
#
|
||||
# Ejecutar: ruby adn/tools/scripts/dasuten_restore_sql4.rb
|
||||
# O como dron: ./adn/tools/run dron lanzar --nota "Migrar DB DASUTEN a dasu-sql4" --timeout 3600 -- ruby adn/tools/scripts/dasuten_restore_sql4.rb
|
||||
|
||||
require 'fileutils'
|
||||
|
||||
TOOLS_DIR = File.expand_path("..", __dir__)
|
||||
CANDADOS = File.join(TOOLS_DIR, "candados", "candados.rb")
|
||||
WZOMBI = File.join(TOOLS_DIR, "w-zombi", "bin", "w-zombi")
|
||||
TELE_LOG = File.join(TOOLS_DIR, "w-zombi", "data", "telemetria.log")
|
||||
|
||||
BAK_FILE = "sysdasuten_compressed_ADN.bak"
|
||||
SMB_SHARE = "//10.0.10.200/BK_SQL"
|
||||
SMB_KEY = "utnlarioja.intranet:monlaricardo"
|
||||
LOCAL_TMP = "/var/tmp/#{BAK_FILE}"
|
||||
DASU_TMP = "/tmp/#{BAK_FILE}"
|
||||
SQL4_DEST = "F:\\BACKUP\\#{BAK_FILE}"
|
||||
|
||||
def paso(n, desc)
|
||||
puts "\n#{"="*60}"
|
||||
puts " PASO #{n}: #{desc}"
|
||||
puts "#{"="*60}"
|
||||
end
|
||||
|
||||
def run!(desc, cmd)
|
||||
puts "▶ #{desc}"
|
||||
puts " CMD: #{cmd}"
|
||||
result = system(cmd)
|
||||
unless result
|
||||
puts "❌ FALLO: #{desc}"
|
||||
exit 1
|
||||
end
|
||||
puts "✅ OK"
|
||||
end
|
||||
|
||||
def candados_run(key, cmd)
|
||||
system("ruby", CANDADOS, "run", key, cmd)
|
||||
end
|
||||
|
||||
def wzombi_cmd(cmd)
|
||||
system("ruby", WZOMBI, "cmd", cmd)
|
||||
end
|
||||
|
||||
def wait_wzombi_result(timeout: 600, interval: 15)
|
||||
# Obtener el último ID antes del comando
|
||||
sleep 5
|
||||
lines = File.readlines(TELE_LOG, encoding: "utf-8:utf-8")
|
||||
last_exec = lines.reverse.find { |l| l.include?("EJECUTANDO") }
|
||||
return nil unless last_exec
|
||||
|
||||
id = last_exec.match(/\[ID:(\d+)\]/)[1]
|
||||
puts " ⏳ Esperando resultado ID:#{id}..."
|
||||
|
||||
elapsed = 0
|
||||
loop do
|
||||
sleep interval
|
||||
elapsed += interval
|
||||
|
||||
current_lines = File.readlines(TELE_LOG, encoding: "utf-8:utf-8")
|
||||
result = current_lines.reverse.find { |l| l.include?("[ID:#{id}]") && (l.include?("RESULTADO") || l.include?("ERROR")) }
|
||||
|
||||
if result
|
||||
if result.include?("ERROR")
|
||||
puts " ❌ Error: #{result.strip}"
|
||||
return nil
|
||||
end
|
||||
puts " ✅ Resultado recibido (ID:#{id})"
|
||||
return result
|
||||
end
|
||||
|
||||
if elapsed >= timeout
|
||||
puts " ⏰ Timeout (#{timeout}s)"
|
||||
return nil
|
||||
end
|
||||
|
||||
puts " ⏳ #{elapsed}s/#{timeout}s..."
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
paso(1, "Descargar backup comprimido desde srvv-fenix vía SMB")
|
||||
# ============================================================
|
||||
# Crear script wrapper para evitar problemas de escape con candados run
|
||||
smb_script = "/tmp/smb_download.sh"
|
||||
File.write(smb_script, <<~SH)
|
||||
#!/bin/bash
|
||||
smbclient #{SMB_SHARE} -U "monlaricardo%$PASS" -W UTNlarioja -c "lcd /var/tmp; get #{BAK_FILE}"
|
||||
SH
|
||||
File.chmod(0755, smb_script)
|
||||
run!("SMB download #{BAK_FILE}", "ruby #{CANDADOS} run #{SMB_KEY} #{smb_script}")
|
||||
|
||||
# Verificar tamaño
|
||||
size = File.size(LOCAL_TMP) rescue 0
|
||||
puts " 📦 Archivo descargado: #{(size / 1024.0 / 1024.0).round(1)} MB"
|
||||
if size < 1_000_000
|
||||
puts "❌ Archivo demasiado pequeño, posible error de descarga"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
paso(2, "Transferir backup a srv-dasu vía SCP")
|
||||
# ============================================================
|
||||
scp_script = "/tmp/scp_to_dasu.sh"
|
||||
File.write(scp_script, <<~SH)
|
||||
#!/bin/bash
|
||||
sshpass -p "$PASS" scp -o StrictHostKeyChecking=no #{LOCAL_TMP} root@srv-dasu:#{DASU_TMP}
|
||||
SH
|
||||
File.chmod(0755, scp_script)
|
||||
run!("SCP a srv-dasu", "ruby #{CANDADOS} run srv-dasu:root #{scp_script}")
|
||||
|
||||
# ============================================================
|
||||
paso(3, "Servir archivo desde srv-dasu por HTTP")
|
||||
# ============================================================
|
||||
http_script = "/tmp/http_srv_dasu.sh"
|
||||
File.write(http_script, <<~SH)
|
||||
#!/bin/bash
|
||||
sshpass -p "$PASS" ssh -o StrictHostKeyChecking=no root@srv-dasu 'pkill -f "http.server 9090" 2>/dev/null; cd /tmp && nohup python3 -m http.server 9090 > /dev/null 2>&1 &'
|
||||
SH
|
||||
File.chmod(0755, http_script)
|
||||
run!("HTTP server en srv-dasu:9090", "ruby #{CANDADOS} run srv-dasu:root #{http_script}")
|
||||
sleep 3
|
||||
|
||||
# ============================================================
|
||||
paso(4, "Descargar backup en dasu-sql4 vía W-Zombi")
|
||||
# ============================================================
|
||||
puts "▶ Enviando comando de descarga a dasu-sql4..."
|
||||
wzombi_cmd("iwr http://192.168.1.13:9090/#{BAK_FILE} -OutFile #{SQL4_DEST}")
|
||||
result = wait_wzombi_result(timeout: 600, interval: 20)
|
||||
unless result
|
||||
puts "❌ No se pudo descargar el archivo en dasu-sql4"
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Verificar que llegó
|
||||
wzombi_cmd("(Get-Item '#{SQL4_DEST}').Length")
|
||||
size_result = wait_wzombi_result(timeout: 60, interval: 10)
|
||||
puts " 📦 Tamaño en destino: #{size_result&.strip}"
|
||||
|
||||
# ============================================================
|
||||
paso(5, "Restaurar base de datos DASUTEN en dasu-sql4")
|
||||
# ============================================================
|
||||
# Primero obtener los nombres lógicos del backup
|
||||
puts "▶ Listando archivos lógicos del backup..."
|
||||
sqlcmd_path = 'C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXE'
|
||||
wzombi_cmd("& '#{sqlcmd_path}' -S localhost -U sa -P UTNlarioja00DASU -Q \"RESTORE FILELISTONLY FROM DISK=N'#{SQL4_DEST}'\" -h -1 -W")
|
||||
filelist = wait_wzombi_result(timeout: 120, interval: 15)
|
||||
puts " 📋 FileList: #{filelist&.strip}"
|
||||
|
||||
# Restaurar con rutas en disco F:
|
||||
puts "\n▶ Ejecutando RESTORE DATABASE..."
|
||||
restore_sql = "RESTORE DATABASE [sysdasuten] FROM DISK=N'#{SQL4_DEST}' WITH REPLACE, MOVE N'sysdasuten_Data' TO N'F:\\DATA\\sysdasuten.mdf', MOVE N'sysdasuten_Log' TO N'F:\\LOG\\sysdasuten_log.ldf'"
|
||||
wzombi_cmd("& '#{sqlcmd_path}' -S localhost -U sa -P UTNlarioja00DASU -Q \"#{restore_sql}\" -t 600")
|
||||
restore_result = wait_wzombi_result(timeout: 900, interval: 30)
|
||||
|
||||
if restore_result && !restore_result.include?("ERROR")
|
||||
puts "✅ BASE DE DATOS RESTAURADA EXITOSAMENTE"
|
||||
else
|
||||
puts "⚠️ Verificar resultado del restore manualmente"
|
||||
end
|
||||
|
||||
# ============================================================
|
||||
paso(6, "Verificación final")
|
||||
# ============================================================
|
||||
wzombi_cmd("& '#{sqlcmd_path}' -S localhost -U sa -P UTNlarioja00DASU -Q \"SELECT name, state_desc, size*8/1024 AS size_mb FROM sys.master_files WHERE database_id = DB_ID('sysdasuten')\" -h -1 -W")
|
||||
verify = wait_wzombi_result(timeout: 60, interval: 10)
|
||||
puts " 📊 Estado de archivos: #{verify&.strip}"
|
||||
|
||||
# ============================================================
|
||||
paso(7, "Limpieza de temporales")
|
||||
# ============================================================
|
||||
File.delete(LOCAL_TMP) if File.exist?(LOCAL_TMP)
|
||||
puts " 🗑️ Eliminado #{LOCAL_TMP}"
|
||||
|
||||
# Limpiar en srv-dasu
|
||||
system("ruby #{CANDADOS} run srv-dasu:root \"sshpass -p '$PASS' ssh -o StrictHostKeyChecking=no root@srv-dasu 'rm -f #{DASU_TMP}; pkill -f http.server.9090'\"")
|
||||
puts " 🗑️ Limpieza en srv-dasu"
|
||||
|
||||
puts "\n#{"="*60}"
|
||||
puts " 🎯 MIGRACIÓN DASUTEN A dasu-sql4 COMPLETADA"
|
||||
puts "#{"="*60}"
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Monitor de instalación SQL Server en dasu-sql4 vía W-Zombi
|
||||
# Ejecutar como dron: ./adn/tools/run dron lanzar --nota "Monitor SQL Install dasu-sql4" -- ruby adn/tools/scripts/dasuten_sql_monitor.rb
|
||||
|
||||
require 'json'
|
||||
require 'fileutils'
|
||||
|
||||
TELE_LOG = File.expand_path("../w-zombi/data/telemetria.log", __dir__)
|
||||
WZOMBI = File.expand_path("../w-zombi/bin/w-zombi", __dir__)
|
||||
|
||||
def wzombi_cmd(cmd)
|
||||
system("ruby", WZOMBI, "cmd", cmd)
|
||||
end
|
||||
|
||||
def last_result_for(id_prefix)
|
||||
lines = File.readlines(TELE_LOG, encoding: "utf-8")
|
||||
result_line = lines.reverse.find { |l| l.include?("RESULTADO [ID:#{id_prefix}]") }
|
||||
return nil unless result_line
|
||||
result_line
|
||||
end
|
||||
|
||||
def wait_for_result(target_id, timeout: 600, interval: 30)
|
||||
elapsed = 0
|
||||
loop do
|
||||
lines = File.readlines(TELE_LOG, encoding: "utf-8")
|
||||
result = lines.reverse.find { |l| l.include?("[ID:#{target_id}]") && (l.include?("RESULTADO") || l.include?("ERROR")) }
|
||||
if result
|
||||
puts "✅ Resultado detectado: #{result.strip}"
|
||||
return result
|
||||
end
|
||||
elapsed += interval
|
||||
if elapsed >= timeout
|
||||
puts "⏰ Timeout esperando resultado de ID:#{target_id} (#{timeout}s)"
|
||||
return nil
|
||||
end
|
||||
puts "⏳ Esperando resultado ID:#{target_id}... (#{elapsed}s/#{timeout}s)"
|
||||
sleep interval
|
||||
end
|
||||
end
|
||||
|
||||
def get_last_id
|
||||
lines = File.readlines(TELE_LOG, encoding: "utf-8")
|
||||
ids = lines.grep(/EJECUTANDO \[ID:(\d+)\]/) { $1.to_i }
|
||||
ids.max
|
||||
end
|
||||
|
||||
# === FASE 1: Esperar que termine la instalación SQL (ID:221) ===
|
||||
puts "🔍 Fase 1: Esperando finalización de instalación SQL Server (ID:221)..."
|
||||
result = wait_for_result(221, timeout: 900, interval: 30)
|
||||
|
||||
if result.nil?
|
||||
puts "❌ Timeout esperando instalación SQL. Abortando."
|
||||
exit 1
|
||||
end
|
||||
|
||||
if result.include?("ERROR")
|
||||
puts "❌ Error en instalación SQL: #{result.strip}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
puts "✅ Instalación SQL completada."
|
||||
|
||||
# === FASE 2: Abrir firewall ===
|
||||
puts "\n🔥 Fase 2: Configurando regla de firewall para puerto 1433..."
|
||||
wzombi_cmd("New-NetFirewallRule -DisplayName SQL-Server -Direction Inbound -LocalPort 1433 -Protocol TCP -Action Allow -Enabled True")
|
||||
sleep 20
|
||||
|
||||
fw_id = get_last_id
|
||||
fw_result = wait_for_result(fw_id, timeout: 60)
|
||||
puts fw_result ? "✅ Firewall configurado." : "⚠️ Sin confirmación de firewall."
|
||||
|
||||
# === FASE 3: Verificar servicio SQL ===
|
||||
puts "\n🔍 Fase 3: Verificando servicio MSSQLSERVER..."
|
||||
wzombi_cmd("Get-Service MSSQLSERVER | Select-Object Status, StartType | ConvertTo-Json")
|
||||
sleep 20
|
||||
|
||||
svc_id = get_last_id
|
||||
svc_result = wait_for_result(svc_id, timeout: 60)
|
||||
if svc_result
|
||||
puts "✅ Estado del servicio SQL: #{svc_result.strip}"
|
||||
else
|
||||
puts "⚠️ No se pudo verificar el servicio."
|
||||
end
|
||||
|
||||
# === FASE 4: Test sqlcmd ===
|
||||
puts "\n🔍 Fase 4: Verificando login SA con sqlcmd..."
|
||||
wzombi_cmd('sqlcmd -S localhost -U sa -P UTNlarioja00DASU -Q "SELECT @@VERSION"')
|
||||
sleep 20
|
||||
|
||||
sql_id = get_last_id
|
||||
sql_result = wait_for_result(sql_id, timeout: 60)
|
||||
if sql_result
|
||||
puts "✅ SQL Server respondió: #{sql_result.strip}"
|
||||
else
|
||||
puts "⚠️ No se pudo verificar sqlcmd."
|
||||
end
|
||||
|
||||
puts "\n🎯 === MONITOR COMPLETADO ==="
|
||||
puts "SQL Server 2019 instalado y verificado en dasu-sql4."
|
||||
puts "Próximo paso: transferir y restaurar sysdasuten_compressed_ADN.bak"
|
||||
@@ -1 +1 @@
|
||||
{"id":200,"cmd":"Rename-Computer -NewName dasu-sql3"}
|
||||
{"id":237,"cmd":"Stop-Service sshd; (Get-Content C:\\ProgramData\\ssh\\sshd_config) -replace \\\"#Port 22\\\",\\\"Port 7022\\\" | Set-Content C:\\ProgramData\\ssh\\sshd_config; Start-Service sshd; New-NetFirewallRule -Name sshd7022 -DisplayName OpenSSH-7022 -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 7022; Get-Service sshd | Select-Object Status"}
|
||||
@@ -7,9 +7,10 @@ module WZombi
|
||||
@config = config
|
||||
end
|
||||
|
||||
def call(_req, res)
|
||||
# Priorizamos host desde variable de entorno, sino usamos la IP Tailscale conocida
|
||||
host = ENV.fetch("WZ_HOST", "100.111.195.4:#{@config.puerto}")
|
||||
def call(req, res)
|
||||
# Priorizamos host desde variable de entorno, luego el header Host (soporte relay), sino fallback a env/tailscale
|
||||
req_host = req.host ? "#{req.host}:#{req.port}" : nil
|
||||
host = ENV["WZ_HOST"] || req_host || "100.111.195.4:#{@config.puerto}"
|
||||
res.body = Engine::PS1Builder.build(host: host)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user