InstanceConfig was a ~50-field struct designed for Wild Cloud k8s instances,
but only 3 fields were ever read by dnsmasq (the sole consumer). The
reconciliation bridge constructed fake InstanceConfig objects from registered
domains just to satisfy this interface.
Replace with DNSEntry{Domain, IP} — a 2-field struct purpose-built for
dnsmasq. All domains get local=/ directives to prevent AAAA queries from
leaking to upstream DNS (Happy Eyeballs / RFC 8305 latency on LAN).
Removed dead code: InstanceConfig, NodeConfig, LoadCloudConfig,
SaveCloudConfig, DeepMerge, LoadMergedInstanceConfig, EnsureInstanceConfig,
instance path helpers, instanceLoadBalancerIP, GenerateInstanceConfig,
and all modular per-instance dnsmasq methods.
107 lines
2.9 KiB
Go
107 lines
2.9 KiB
Go
package dnsmasq
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/wild-cloud/wild-central/internal/config"
|
|
"github.com/wild-cloud/wild-central/internal/network"
|
|
)
|
|
|
|
// GenerateMainConfig creates the main dnsmasq configuration with global settings
|
|
// and a conf-dir directive to include per-domain configs
|
|
func (g *ConfigGenerator) GenerateMainConfig(cfg *config.State) string {
|
|
// Get the Wild Central IP address
|
|
dnsIP, err := network.GetWildCentralIP()
|
|
if err != nil {
|
|
slog.Error("failed to detect Wild Central IP", "component", "dnsmasq", "error", err)
|
|
dnsIP = ""
|
|
}
|
|
|
|
var sb strings.Builder
|
|
|
|
fmt.Fprintf(&sb, `# Wild Cloud DNS Configuration (Main)
|
|
# This file contains global settings. Domain-specific DNS entries are
|
|
# generated by reconciliation into the monolithic config.
|
|
|
|
# Basic Settings
|
|
listen-address=%s
|
|
bind-interfaces
|
|
domain-needed
|
|
bogus-priv
|
|
no-resolv
|
|
|
|
# Upstream DNS servers
|
|
server=1.1.1.1
|
|
server=8.8.8.8
|
|
|
|
# Logging
|
|
log-queries
|
|
log-dhcp
|
|
`, dnsIP)
|
|
|
|
// DHCP section (only when explicitly enabled)
|
|
if cfg != nil && cfg.Cloud.Dnsmasq.DHCP.Enabled {
|
|
dhcp := cfg.Cloud.Dnsmasq.DHCP
|
|
sb.WriteString("\n# DHCP Configuration\n")
|
|
|
|
leaseTime := dhcp.LeaseTime
|
|
if leaseTime == "" {
|
|
leaseTime = "24h"
|
|
}
|
|
fmt.Fprintf(&sb, "dhcp-range=%s,%s,%s\n", dhcp.RangeStart, dhcp.RangeEnd, leaseTime)
|
|
|
|
gateway := dhcp.Gateway
|
|
if gateway != "" {
|
|
fmt.Fprintf(&sb, "dhcp-option=3,%s\n", gateway) // default gateway
|
|
}
|
|
if dnsIP != "" {
|
|
fmt.Fprintf(&sb, "dhcp-option=6,%s\n", dnsIP) // DNS server
|
|
}
|
|
|
|
for _, lease := range dhcp.StaticLeases {
|
|
if lease.Hostname != "" {
|
|
fmt.Fprintf(&sb, "dhcp-host=%s,%s,%s\n", lease.MAC, lease.IP, lease.Hostname)
|
|
} else {
|
|
fmt.Fprintf(&sb, "dhcp-host=%s,%s\n", lease.MAC, lease.IP)
|
|
}
|
|
}
|
|
}
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
// ValidateConfig tests a dnsmasq configuration file for syntax errors
|
|
func (g *ConfigGenerator) ValidateConfig(configPath string) error {
|
|
// Use dnsmasq --test to validate the configuration
|
|
cmd := exec.Command("dnsmasq", "--test", "-C", configPath)
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("config validation failed: %w (output: %s)", err, string(output))
|
|
}
|
|
|
|
// Check if output contains "syntax check OK"
|
|
if !strings.Contains(string(output), "syntax check OK") {
|
|
return fmt.Errorf("config validation did not report OK: %s", string(output))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReloadService sends a SIGHUP to dnsmasq to reload configuration
|
|
// This is lighter weight than a full restart
|
|
func (g *ConfigGenerator) ReloadService() error {
|
|
// Use systemctl reload which sends SIGHUP
|
|
cmd := exec.Command("systemctl", "reload", "dnsmasq.service")
|
|
_, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
// If reload fails, try restart as fallback
|
|
slog.Error("reload failed, attempting restart", "component", "dnsmasq", "error", err)
|
|
return g.RestartService()
|
|
}
|
|
slog.Info("dnsmasq service reloaded", "component", "dnsmasq")
|
|
return nil
|
|
}
|