# frozen_string_literal: true # DNS CLI — Gestión del servidor DNS (srvv-dns) # # Responsabilidad: Comandos para administración de CoreDNS # # Uso: # ./adn/tools/run dns help # Ayuda # ./adn/tools/run dns status # Estado del servidor # ./adn/tools/run dns zona # Listar zona activa # ./adn/tools/run dns registros # Listar todos los registros # ./adn/tools/run dns buscar # ./adn/tools/run dns backup # Crear backup de zona # ./adn/tools/run dns reload # Forzar reload de CoreDNS require 'open3' require 'time' require_relative '../core/colores' module ADN class SubcomandoDNS SSH_HOST = 'rmonla@10.0.10.2' SSH_OPTS = '-o StrictHostKeyChecking=no -o ConnectTimeout=10' ZONA_PATH = '/docker/coreDNS/zones/frlr.utn.edu.ar.db' COREFILE_PATH = '/docker/coreDNS/Corefile' ZONA_NAME = 'frlr.utn.edu.ar' def initialize(args, logger) @args = args @logger = logger end def ejecutar if @args.empty? || @args[0] == 'help' mostrar_ayuda return end comando = @args[0] case comando when 'status' status when 'zona' zona when 'registros' registros when 'buscar' buscar(@args[1]) when 'backup' backup when 'reload' reload when 'corefile' corefile else puts "#{Color::RED}✗ Comando DNS desconocido: #{comando}#{Color::RESET}" mostrar_ayuda exit 1 end end private def mostrar_ayuda puts <<~HELP #{Color::CYAN}🌐 DNS CLI — Gestión del servidor DNS (srvv-dns)#{Color::RESET} #{Color::GREEN}Uso:#{Color::RESET} ./adn/tools/run dns [args] #{Color::YELLOW}━━━ Comandos de Consulta ━━━#{Color::RESET} #{Color::BOLD}status#{Color::RESET} Estado del servidor CoreDNS #{Color::BOLD}zona#{Color::RESET} Información de la zona #{ZONA_NAME} #{Color::BOLD}registros#{Color::RESET} Listar todos los registros DNS #{Color::BOLD}buscar #{Color::RESET} Buscar registros por nombre #{Color::YELLOW}━━━ Comandos de Mantenimiento ━━━#{Color::RESET} #{Color::BOLD}backup#{Color::RESET} Crear backup de la zona #{Color::BOLD}reload#{Color::RESET} Forzar reload de CoreDNS #{Color::BOLD}corefile#{Color::RESET} Mostrar configuración CoreDNS #{Color::YELLOW}Ejemplos:#{Color::RESET} #{Color::DIM}# Ver estado del servidor#{Color::RESET} ./adn/tools/run dns status #{Color::DIM}# Listar todos los registros#{Color::RESET} ./adn/tools/run dns registros #{Color::DIM}# Buscar registros de ns8#{Color::RESET} ./adn/tools/run dns buscar ns8 #{Color::DIM}# Crear backup antes de cambios#{Color::RESET} ./adn/tools/run dns backup HELP end def status puts "#{Color::CYAN}🌐 Estado del servidor DNS (srvv-dns)#{Color::RESET}\n\n" # Verificar conectividad print " Conectividad SSH... " if ssh_exec_simple('true') puts "#{Color::GREEN}✅ OK#{Color::RESET}" else puts "#{Color::RED}❌ Fallido#{Color::RESET}" return end # Verificar contenedor CoreDNS print " Contenedor CoreDNS... " output = ssh_exec('docker ps --filter "name=coredns" --format "{{.Status}}"') if output&.include?('Up') puts "#{Color::GREEN}✅ Activo (#{output.strip})#{Color::RESET}" else puts "#{Color::RED}❌ Inactivo#{Color::RESET}" end # Verificar puerto 53 print " Puerto 53... " output = ssh_exec('ss -tuln | grep ":53" | head -1') if output&.include?('53') puts "#{Color::GREEN}✅ Escuchando#{Color::RESET}" else puts "#{Color::RED}❌ Cerrado#{Color::RESET}" end # Verificar zona print " Zona #{ZONA_NAME}... " output = ssh_exec("test -f #{ZONA_PATH} && echo 'exists' || echo 'missing'") if output&.include?('exists') puts "#{Color::GREEN}✅ Existente#{Color::RESET}" else puts "#{Color::RED}❌ No encontrada#{Color::RESET}" end # Serial de zona print " Serial de zona... " serial = obtener_serial if serial puts "#{Color::GREEN}#{serial}#{Color::RESET}" else puts "#{Color::RED}❌ No se pudo leer#{Color::RESET}" end puts "\n #{Color::DIM}Última consulta: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}#{Color::RESET}" end def zona puts "#{Color::CYAN}📋 Zona DNS: #{ZONA_NAME}#{Color::RESET}\n\n" serial = obtener_serial puts " SOA Serial: #{serial || 'N/A'}" output = ssh_exec("cat #{ZONA_PATH}") return unless output # Contar registros por tipo registros = { 'A' => 0, 'CNAME' => 0, 'MX' => 0, 'TXT' => 0, 'NS' => 0, 'SRV' => 0 } output.each_line do |line| next if line.start_with?('$', ';') registros.each_key do |tipo| registros[tipo] += 1 if line.include?(" IN #{tipo} ") end end puts "\n Registros por tipo:" registros.each do |tipo, count| puts " #{tipo}: #{count}" if count > 0 end # Nameservers puts "\n Nameservers:" output.each_line do |line| puts " #{line.strip}" if line.include?(' IN NS ') end end def registros output = ssh_exec("cat #{ZONA_PATH}") return unless output puts "#{Color::CYAN}📋 Registros de zona #{ZONA_NAME}#{Color::RESET}\n\n" puts "```\n" # Imprimir solo líneas de registros (no comentarios ni directivas) output.each_line do |line| line = line.strip next if line.empty? || line.start_with?('$', ';', '(', ')') puts line if line.include?(' IN ') end puts "```" end def buscar(nombre) return puts "#{Color::RED}✗ Falta nombre a buscar#{Color::RESET}" unless nombre output = ssh_exec("cat #{ZONA_PATH}") return unless output puts "#{Color::CYAN}🔍 Buscando '#{nombre}' en #{ZONA_NAME}#{Color::RESET}\n\n" encontrados = [] output.each_line do |line| line = line.strip next if line.empty? || line.start_with?('$', ';') encontrados << line if line.include?(nombre) end if encontrados.empty? puts " #{Color::RED}No se encontraron registros#{Color::RESET}" else puts "```\n" encontrados.each { |l| puts l } puts "```" end end def backup timestamp = Time.now.strftime('%y%m%d-%H%M') backup_path = "/docker/coreDNS/zones/bkp_#{timestamp}_frlr.utn.edu.ar.db" puts " Creando backup: #{File.basename(backup_path)}..." output = ssh_exec("cp #{ZONA_PATH} #{backup_path} && ls -la #{backup_path}") if output puts " #{Color::GREEN}✅ Backup creado#{Color::RESET}" else puts " #{Color::RED}❌ Error al crear backup#{Color::RESET}" end end def reload puts " Forzando reload de CoreDNS..." output = ssh_exec("touch #{ZONA_PATH} && docker kill --signal=HUP coredns 2>/dev/null || echo 'reload manual'") if output puts " #{Color::GREEN}✅ Reload solicitado#{Color::RESET}" puts " #{Color::DIM}CoreDNS recargará la zona en ~30s#{Color::RESET}" else puts " #{Color::RED}❌ Error al solicitar reload#{Color::RESET}" end end def corefile puts "#{Color::CYAN}⚙️ Corefile de CoreDNS#{Color::RESET}\n\n" puts "```\n" output = ssh_exec("cat #{COREFILE_PATH}") puts output if output puts "```" end def ssh_exec(cmd) full_cmd = "ssh #{SSH_OPTS} #{SSH_HOST} '#{cmd}'" begin stdout, stderr, status = Open3.capture3(full_cmd) if status.success? stdout.strip else $stderr.puts "#{Color::RED}SSH error: #{stderr.strip}#{Color::RESET}" nil end rescue => e $stderr.puts "#{Color::RED}Error en SSH: #{e.message}#{Color::RESET}" nil end end def ssh_exec_simple(cmd) full_cmd = "ssh #{SSH_OPTS} #{SSH_HOST} '#{cmd}'" begin _, _, status = Open3.capture3(full_cmd) status.success? rescue false end end def obtener_serial output = ssh_exec("grep -A5 'IN SOA' #{ZONA_PATH} | grep -oE '[0-9]{10}' | head -1") output end end end