Files
Ricardo MonlaandClaude Opus 4.6 a013811aa2 feat(A07_dtic-DNS): Ámbito DNS operativo + CLI para gestión de CoreDNS
- 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>
2026-04-07 16:57:58 -03:00

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