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 }