- Crear ámbito A07_dtic-DNS con manifiesto y plan A07.P001 - Implementar CLI dns.rb con 7 subcomandos (status, zona, registros, buscar, backup, reload, corefile) - Detectar servidor srvv-dns: CoreDNS en Docker (10.0.10.2, VM 112 en srv-pmox1) - Documentar zona frlr.utn.edu.ar con 20+ registros (A, CNAME, MX, TXT, SRV) - Actualizar ficha nodos/srvv-dns.md con información técnica completa - Agregar script reparar.sh para ns8-apps (pendiente de ejecución) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
286 lines
8.5 KiB
Ruby
286 lines
8.5 KiB
Ruby
# 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 <nombre>
|
|
# ./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 <subcomando> [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 <nombre>#{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
|