541 lines
17 KiB
Ruby
Executable File
541 lines
17 KiB
Ruby
Executable File
#!/usr/bin/env ruby
|
||
# frozen_string_literal: true
|
||
|
||
# tools/orquestador/dashboard/sincronizar.rb
|
||
# ==========================================
|
||
# Herramienta para sincronizar el dashboard local con producción
|
||
# Filosofía: "Menos es más" - herramienta pequeña, específica y reutilizable
|
||
|
||
require 'fileutils'
|
||
require 'time'
|
||
|
||
class DashboardSynchronizer
|
||
# Configuración de rutas
|
||
CONFIG = {
|
||
local: {
|
||
dashboard_dir: File.expand_path('../../../../dashboard', __FILE__),
|
||
index_file: 'index.html',
|
||
backups_dir: 'backups'
|
||
},
|
||
production: {
|
||
dashboard_dir: '/var/www/html/P2601',
|
||
index_file: 'index.html',
|
||
backups_dir: '/var/www/html/P2601/backups'
|
||
}
|
||
}.freeze
|
||
|
||
def initialize(options = {})
|
||
@dry_run = options[:dry_run] || false
|
||
@verbose = options[:verbose] || false
|
||
@backup = options[:backup] || true
|
||
@timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
||
end
|
||
|
||
def sincronizar_a_produccion
|
||
puts "🔄 SINCRONIZANDO DASHBOARD A PRODUCCIÓN"
|
||
puts "=" * 60
|
||
|
||
# Verificar rutas locales
|
||
unless verificar_ruta_local
|
||
puts "❌ Error: No se puede acceder al dashboard local"
|
||
return false
|
||
end
|
||
|
||
# Verificar rutas de producción
|
||
unless verificar_ruta_produccion
|
||
puts "❌ Error: No se puede acceder al dashboard de producción"
|
||
puts " Ruta: #{CONFIG[:production][:dashboard_dir]}"
|
||
return false
|
||
end
|
||
|
||
# Crear backup en producción si está habilitado
|
||
if @backup && !@dry_run
|
||
unless crear_backup_produccion
|
||
puts "❌ Error: No se pudo crear backup en producción"
|
||
return false
|
||
end
|
||
end
|
||
|
||
# Sincronizar archivos
|
||
puts "📁 Sincronizando archivos..."
|
||
archivos_sincronizados = sincronizar_archivos
|
||
|
||
# Mostrar resumen
|
||
mostrar_resumen_sincronizacion(archivos_sincronizados)
|
||
|
||
archivos_sincronizados.any?
|
||
end
|
||
|
||
def verificar_estado
|
||
puts "🔍 VERIFICANDO ESTADO DE SINCRONIZACIÓN"
|
||
puts "=" * 60
|
||
|
||
estado = {
|
||
local: verificar_estado_local,
|
||
production: verificar_estado_produccion,
|
||
diferencias: comparar_archivos
|
||
}
|
||
|
||
mostrar_estado_verificacion(estado)
|
||
|
||
estado[:local][:valido] && estado[:production][:valido]
|
||
end
|
||
|
||
def listar_backups
|
||
puts "💾 LISTADO DE BACKUPS EN PRODUCCIÓN"
|
||
puts "=" * 60
|
||
|
||
backups_dir = CONFIG[:production][:backups_dir]
|
||
|
||
unless Dir.exist?(backups_dir)
|
||
puts "ℹ️ No existe el directorio de backups: #{backups_dir}"
|
||
return false
|
||
end
|
||
|
||
backups = Dir.glob(File.join(backups_dir, '*.html')).sort.reverse
|
||
|
||
if backups.empty?
|
||
puts "ℹ️ No se encontraron backups"
|
||
return true
|
||
end
|
||
|
||
puts "📂 Directorio: #{backups_dir}"
|
||
puts "📊 Total de backups: #{backups.size}"
|
||
puts
|
||
|
||
backups.each_with_index do |backup, i|
|
||
nombre = File.basename(backup)
|
||
tamano = File.size(backup) / 1024
|
||
modificacion = File.mtime(backup).strftime('%Y-%m-%d %H:%M:%S')
|
||
|
||
puts " #{i + 1}. #{nombre}"
|
||
puts " • Tamaño: #{tamano} KB"
|
||
puts " • Modificado: #{modificacion}"
|
||
puts
|
||
end
|
||
|
||
true
|
||
end
|
||
|
||
def restaurar_backup(nombre_backup = nil)
|
||
puts "🔄 RESTAURANDO BACKUP DESDE PRODUCCIÓN"
|
||
puts "=" * 60
|
||
|
||
backups_dir = CONFIG[:production][:backups_dir]
|
||
|
||
unless Dir.exist?(backups_dir)
|
||
puts "❌ Error: No existe el directorio de backups: #{backups_dir}"
|
||
return false
|
||
end
|
||
|
||
# Si no se especifica backup, listar disponibles
|
||
unless nombre_backup
|
||
backups = Dir.glob(File.join(backups_dir, '*.html')).sort.reverse
|
||
|
||
if backups.empty?
|
||
puts "❌ Error: No hay backups disponibles para restaurar"
|
||
return false
|
||
end
|
||
|
||
puts "📋 Backups disponibles:"
|
||
backups.each_with_index do |backup, i|
|
||
puts " #{i + 1}. #{File.basename(backup)}"
|
||
end
|
||
|
||
puts "\nℹ️ Use: #{$0} restaurar NOMBRE_BACKUP"
|
||
return false
|
||
end
|
||
|
||
# Verificar que el backup existe
|
||
ruta_backup = File.join(backups_dir, nombre_backup)
|
||
unless File.exist?(ruta_backup)
|
||
puts "❌ Error: Backup no encontrado: #{nombre_backup}"
|
||
puts " Ruta: #{ruta_backup}"
|
||
return false
|
||
end
|
||
|
||
# Crear backup del estado actual local
|
||
if @backup && !@dry_run
|
||
puts "💾 Creando backup del estado local actual..."
|
||
crear_backup_local
|
||
end
|
||
|
||
# Restaurar backup
|
||
puts "📥 Restaurando backup: #{nombre_backup}"
|
||
archivo_local = File.join(CONFIG[:local][:dashboard_dir], CONFIG[:local][:index_file])
|
||
|
||
if @dry_run
|
||
puts " [DRY RUN] Se restauraría #{ruta_backup} a #{archivo_local}"
|
||
puts " [DRY RUN] Tamaño: #{File.size(ruta_backup)} bytes"
|
||
else
|
||
begin
|
||
FileUtils.cp(ruta_backup, archivo_local)
|
||
puts "✅ Backup restaurado exitosamente"
|
||
puts " • Origen: #{ruta_backup}"
|
||
puts " • Destino: #{archivo_local}"
|
||
puts " • Tamaño: #{File.size(archivo_local)} bytes"
|
||
rescue => e
|
||
puts "❌ Error al restaurar backup: #{e.message}"
|
||
return false
|
||
end
|
||
end
|
||
|
||
true
|
||
end
|
||
|
||
private
|
||
|
||
def verificar_ruta_local
|
||
dir = CONFIG[:local][:dashboard_dir]
|
||
archivo = File.join(dir, CONFIG[:local][:index_file])
|
||
|
||
unless Dir.exist?(dir)
|
||
puts "❌ Directorio local no existe: #{dir}"
|
||
return false
|
||
end
|
||
|
||
unless File.exist?(archivo)
|
||
puts "❌ Archivo local no existe: #{archivo}"
|
||
return false
|
||
end
|
||
|
||
true
|
||
end
|
||
|
||
def verificar_ruta_produccion
|
||
dir = CONFIG[:production][:dashboard_dir]
|
||
|
||
unless Dir.exist?(dir)
|
||
puts "❌ Directorio de producción no existe: #{dir}"
|
||
return false
|
||
end
|
||
|
||
# Verificar permisos de escritura
|
||
unless File.writable?(dir)
|
||
puts "❌ Sin permisos de escritura en: #{dir}"
|
||
return false
|
||
end
|
||
|
||
true
|
||
end
|
||
|
||
def crear_backup_produccion
|
||
puts "💾 Creando backup en producción..."
|
||
|
||
backups_dir = CONFIG[:production][:backups_dir]
|
||
archivo_produccion = File.join(CONFIG[:production][:dashboard_dir], CONFIG[:production][:index_file])
|
||
|
||
# Crear directorio de backups si no existe
|
||
unless Dir.exist?(backups_dir)
|
||
puts " Creando directorio de backups: #{backups_dir}"
|
||
FileUtils.mkdir_p(backups_dir)
|
||
end
|
||
|
||
# Nombre del backup
|
||
nombre_backup = "dashboard_prod_#{@timestamp}.html"
|
||
ruta_backup = File.join(backups_dir, nombre_backup)
|
||
|
||
# Crear backup
|
||
begin
|
||
FileUtils.cp(archivo_produccion, ruta_backup)
|
||
puts "✅ Backup creado: #{nombre_backup}"
|
||
puts " • Ruta: #{ruta_backup}"
|
||
puts " • Tamaño: #{File.size(ruta_backup)} bytes"
|
||
true
|
||
rescue => e
|
||
puts "❌ Error al crear backup: #{e.message}"
|
||
false
|
||
end
|
||
end
|
||
|
||
def crear_backup_local
|
||
backups_dir = File.join(CONFIG[:local][:dashboard_dir], CONFIG[:local][:backups_dir])
|
||
archivo_local = File.join(CONFIG[:local][:dashboard_dir], CONFIG[:local][:index_file])
|
||
|
||
# Crear directorio de backups si no existe
|
||
unless Dir.exist?(backups_dir)
|
||
FileUtils.mkdir_p(backups_dir)
|
||
end
|
||
|
||
# Nombre del backup
|
||
nombre_backup = "dashboard_local_#{@timestamp}.html"
|
||
ruta_backup = File.join(backups_dir, nombre_backup)
|
||
|
||
# Crear backup
|
||
FileUtils.cp(archivo_local, ruta_backup)
|
||
end
|
||
|
||
def sincronizar_archivos
|
||
archivos_sincronizados = []
|
||
|
||
# Archivo principal (index.html)
|
||
archivo_local = File.join(CONFIG[:local][:dashboard_dir], CONFIG[:local][:index_file])
|
||
archivo_produccion = File.join(CONFIG[:production][:dashboard_dir], CONFIG[:production][:index_file])
|
||
|
||
puts "📄 Sincronizando: #{CONFIG[:local][:index_file]}"
|
||
puts " • Local: #{archivo_local}"
|
||
puts " • Producción: #{archivo_produccion}"
|
||
|
||
# Verificar si hay diferencias
|
||
if archivos_iguales?(archivo_local, archivo_produccion)
|
||
puts " ℹ️ Los archivos son idénticos, no se requiere sincronización"
|
||
else
|
||
if @dry_run
|
||
puts " [DRY RUN] Se copiaría #{archivo_local} a #{archivo_produccion}"
|
||
puts " [DRY RUN] Diferencia de tamaño: #{File.size(archivo_local) - File.size(archivo_produccion)} bytes"
|
||
else
|
||
begin
|
||
FileUtils.cp(archivo_local, archivo_produccion)
|
||
puts " ✅ Sincronizado exitosamente"
|
||
archivos_sincronizados << CONFIG[:local][:index_file]
|
||
rescue => e
|
||
puts " ❌ Error al sincronizar: #{e.message}"
|
||
end
|
||
end
|
||
end
|
||
|
||
# Buscar otros archivos en el directorio local
|
||
Dir.glob(File.join(CONFIG[:local][:dashboard_dir], '*')).each do |archivo_local|
|
||
next if File.directory?(archivo_local)
|
||
next if File.basename(archivo_local) == CONFIG[:local][:index_file]
|
||
|
||
archivo_produccion = File.join(CONFIG[:production][:dashboard_dir], File.basename(archivo_local))
|
||
|
||
puts "📄 Sincronizando: #{File.basename(archivo_local)}"
|
||
|
||
if @dry_run
|
||
puts " [DRY RUN] Se copiaría #{archivo_local} a #{archivo_produccion}"
|
||
else
|
||
begin
|
||
FileUtils.cp(archivo_local, archivo_produccion)
|
||
puts " ✅ Sincronizado exitosamente"
|
||
archivos_sincronizados << File.basename(archivo_local)
|
||
rescue => e
|
||
puts " ❌ Error al sincronizar: #{e.message}"
|
||
end
|
||
end
|
||
end
|
||
|
||
archivos_sincronizados
|
||
end
|
||
|
||
def archivos_iguales?(archivo1, archivo2)
|
||
return false unless File.exist?(archivo1) && File.exist?(archivo2)
|
||
return false unless File.size(archivo1) == File.size(archivo2)
|
||
|
||
# Comparación simple por tamaño y contenido
|
||
File.read(archivo1) == File.read(archivo2)
|
||
end
|
||
|
||
def verificar_estado_local
|
||
dir = CONFIG[:local][:dashboard_dir]
|
||
archivo = File.join(dir, CONFIG[:local][:index_file])
|
||
|
||
{
|
||
valido: File.exist?(archivo),
|
||
ruta: dir,
|
||
archivo: CONFIG[:local][:index_file],
|
||
existe: File.exist?(archivo),
|
||
tamano: File.exist?(archivo) ? File.size(archivo) : 0,
|
||
modificacion: File.exist?(archivo) ? File.mtime(archivo).strftime('%Y-%m-%d %H:%M:%S') : 'N/A'
|
||
}
|
||
end
|
||
|
||
def verificar_estado_produccion
|
||
dir = CONFIG[:production][:dashboard_dir]
|
||
archivo = File.join(dir, CONFIG[:production][:index_file])
|
||
|
||
{
|
||
valido: File.exist?(archivo),
|
||
ruta: dir,
|
||
archivo: CONFIG[:production][:index_file],
|
||
existe: File.exist?(archivo),
|
||
tamano: File.exist?(archivo) ? File.size(archivo) : 0,
|
||
modificacion: File.exist?(archivo) ? File.mtime(archivo).strftime('%Y-%m-%d %H:%M:%S') : 'N/A',
|
||
escribible: File.writable?(dir)
|
||
}
|
||
end
|
||
|
||
def comparar_archivos
|
||
archivo_local = File.join(CONFIG[:local][:dashboard_dir], CONFIG[:local][:index_file])
|
||
archivo_produccion = File.join(CONFIG[:production][:dashboard_dir], CONFIG[:production][:index_file])
|
||
|
||
return {} unless File.exist?(archivo_local) && File.exist?(archivo_produccion)
|
||
|
||
{
|
||
iguales: archivos_iguales?(archivo_local, archivo_produccion),
|
||
tamano_local: File.size(archivo_local),
|
||
tamano_produccion: File.size(archivo_produccion),
|
||
diferencia_tamano: File.size(archivo_local) - File.size(archivo_produccion),
|
||
modificacion_local: File.mtime(archivo_local),
|
||
modificacion_produccion: File.mtime(archivo_produccion),
|
||
mas_reciente: File.mtime(archivo_local) > File.mtime(archivo_produccion) ? 'local' : 'produccion'
|
||
}
|
||
end
|
||
|
||
def mostrar_resumen_sincronizacion(archivos_sincronizados)
|
||
puts "\n📊 RESUMEN DE SINCRONIZACIÓN"
|
||
puts "=" * 60
|
||
|
||
if @dry_run
|
||
puts "🔍 MODO SIMULACIÓN (DRY RUN)"
|
||
puts " No se realizaron cambios reales"
|
||
end
|
||
|
||
if archivos_sincronizados.empty?
|
||
puts "ℹ️ No se sincronizaron archivos"
|
||
puts " • Los archivos ya están sincronizados"
|
||
puts " • O hubo errores en la sincronización"
|
||
else
|
||
puts "✅ Archivos sincronizados: #{archivos_sincronizados.size}"
|
||
archivos_sincronizados.each do |archivo|
|
||
puts " • #{archivo}"
|
||
end
|
||
end
|
||
|
||
# Mostrar URLs
|
||
puts "\n🔗 URLs DEL DASHBOARD:"
|
||
puts " • Local: file://#{CONFIG[:local][:dashboard_dir]}/#{CONFIG[:local][:index_file]}"
|
||
puts " • Producción: https://ns8.frlr.utn.edu.ar/P2601/"
|
||
puts " • Bitácoras: https://ns8.frlr.utn.edu.ar/bitacoras/p2601"
|
||
|
||
puts "\n💡 RECOMENDACIONES:"
|
||
puts " 1. Verificar cambios en producción: https://ns8.frlr.utn.edu.ar/P2601/"
|
||
puts " 2. Usar 'verificar' para validar la sincronización"
|
||
puts " 3. Revisar backups si es necesario restaurar"
|
||
end
|
||
|
||
def mostrar_estado_verificacion(estado)
|
||
puts "🏠 ESTADO LOCAL:"
|
||
if estado[:local][:valido]
|
||
puts " ✅ Dashboard local válido"
|
||
puts " • Ruta: #{estado[:local][:ruta]}"
|
||
puts " • Archivo: #{estado[:local][:archivo]}"
|
||
puts " • Tamaño: #{estado[:local][:tamano]} bytes"
|
||
puts " • Modificación: #{estado[:local][:modificacion]}"
|
||
else
|
||
puts " ❌ Dashboard local no válido"
|
||
end
|
||
|
||
puts "\n🏭 ESTADO PRODUCCIÓN:"
|
||
if estado[:production][:valido]
|
||
puts " ✅ Dashboard de producción válido"
|
||
puts " • Ruta: #{estado[:production][:ruta]}"
|
||
puts " • Archivo: #{estado[:production][:archivo]}"
|
||
puts " • Tamaño: #{estado[:production][:tamano]} bytes"
|
||
puts " • Modificación: #{estado[:production][:modificacion]}"
|
||
puts " • Escribible: #{estado[:production][:escribible] ? '✅ Sí' : '❌ No'}"
|
||
else
|
||
puts " ❌ Dashboard de producción no válido"
|
||
end
|
||
|
||
if estado[:diferencias].any?
|
||
puts "\n🔍 COMPARACIÓN DE ARCHIVOS:"
|
||
if estado[:diferencias][:iguales]
|
||
puts " ✅ Los archivos son idénticos"
|
||
else
|
||
puts " ⚠️ Los archivos son diferentes"
|
||
puts " • Tamaño local: #{estado[:diferencias][:tamano_local]} bytes"
|
||
puts " • Tamaño producción: #{estado[:diferencias][:tamano_produccion]} bytes"
|
||
puts " • Diferencia: #{estado[:diferencias][:diferencia_tamano]} bytes"
|
||
puts " • Más reciente: #{estado[:diferencias][:mas_reciente]}"
|
||
end
|
||
end
|
||
|
||
puts "\n💡 RECOMENDACIONES:"
|
||
if estado[:local][:valido] && estado[:production][:valido]
|
||
if estado[:diferencias][:iguales]
|
||
puts " ✅ Los dashboards están sincronizados"
|
||
puts " • No se requiere acción"
|
||
else
|
||
puts " ⚠️ Los dashboards están desincronizados"
|
||
puts " • Use 'sincronizar' para actualizar producción"
|
||
puts " • Use 'sincronizar --dry-run' para simular"
|
||
end
|
||
else
|
||
puts " ❌ Hay problemas con los dashboards"
|
||
puts " • Verifique las rutas y permisos"
|
||
end
|
||
end
|
||
end
|
||
|
||
# Interfaz de línea de comandos
|
||
if __FILE__ == $0
|
||
begin
|
||
synchronizer = DashboardSynchronizer.new
|
||
|
||
# Parsear argumentos
|
||
command = ARGV[0] || 'verificar'
|
||
options = {
|
||
dry_run: ARGV.include?('--dry-run') || ARGV.include?('-n'),
|
||
verbose: ARGV.include?('--verbose') || ARGV.include?('-v'),
|
||
backup: !ARGV.include?('--no-backup')
|
||
}
|
||
|
||
# Extraer argumentos adicionales
|
||
extra_args = (ARGV[1..-1] || []).reject { |arg| arg.start_with?('--') || arg == '-n' || arg == '-v' }
|
||
|
||
case command
|
||
when 'sincronizar', 'sync'
|
||
synchronizer = DashboardSynchronizer.new(options)
|
||
success = synchronizer.sincronizar_a_produccion
|
||
when 'verificar', 'check'
|
||
success = synchronizer.verificar_estado
|
||
when 'backups', 'list-backups'
|
||
success = synchronizer.listar_backups
|
||
when 'restaurar', 'restore'
|
||
nombre_backup = extra_args[0]
|
||
synchronizer = DashboardSynchronizer.new(options)
|
||
success = synchronizer.restaurar_backup(nombre_backup)
|
||
when 'help', '--help', '-h'
|
||
puts "📖 USO:"
|
||
puts " #{$0} [comando] [argumentos...]"
|
||
puts
|
||
puts "COMANDOS:"
|
||
puts " sincronizar, sync - Sincronizar dashboard local a producción"
|
||
puts " verificar, check - Verificar estado de sincronización"
|
||
puts " backups, list-backups - Listar backups en producción"
|
||
puts " restaurar, restore [NOMBRE] - Restaurar backup desde producción"
|
||
puts " help - Mostrar esta ayuda"
|
||
puts
|
||
puts "OPCIONES:"
|
||
puts " --dry-run, -n - Simular sin hacer cambios"
|
||
puts " --verbose, -v - Mostrar detalles adicionales"
|
||
puts " --no-backup - No crear backups automáticos"
|
||
puts
|
||
puts "EJEMPLOS:"
|
||
puts " #{$0} sincronizar"
|
||
puts " #{$0} sincronizar --dry-run"
|
||
puts " #{$0} verificar"
|
||
puts " #{$0} backups"
|
||
puts " #{$0} restaurar dashboard_prod_20240306_114500.html"
|
||
puts
|
||
puts "🔗 URLs IMPORTANTES:"
|
||
puts " • Producción: https://ns8.frlr.utn.edu.ar/P2601/"
|
||
puts " • Bitácoras: https://ns8.frlr.utn.edu.ar/bitacoras/p2601"
|
||
puts
|
||
success = true
|
||
else
|
||
puts "❌ Comando '#{command}' no reconocido"
|
||
puts " Use '#{$0} help' para ver la ayuda"
|
||
success = false
|
||
end
|
||
|
||
if success
|
||
puts "\n🚀 PRÓXIMOS PASOS:"
|
||
puts " 1. Verificar cambios en: https://ns8.frlr.utn.edu.ar/P2601/"
|
||
puts " 2. Usar herramientas de orquestador para gestión continua"
|
||
puts " 3. Revisar backups periódicamente"
|
||
puts
|
||
puts "📅 #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
|
||
puts "=" * 60
|
||
else
|
||
exit 1
|
||
end
|
||
rescue => e
|
||
puts "❌ Error: #{e.message}"
|
||
puts e.backtrace if ENV['DEBUG']
|
||
exit 1
|
||
end
|
||
end
|