diff --git a/internal/api/v1/handlers.go b/internal/api/v1/handlers.go index be1f52f..5bd5e94 100644 --- a/internal/api/v1/handlers.go +++ b/internal/api/v1/handlers.go @@ -42,7 +42,7 @@ type API struct { ctx gocontext.Context // parent context for restartable goroutines reconciler *reconcile.Reconciler secrets *secrets.Manager - dnsmasq *dnsmasq.ConfigGenerator + dnsmasq *dnsmasq.Manager haproxy *haproxy.Manager nftables *nftables.Manager ddns *ddns.Runner @@ -72,7 +72,7 @@ func NewAPI(dataDir, version string, allowedOrigins []string) (*API, error) { version: version, allowedOrigins: allowedOrigins, secrets: secrets.NewManager(filepath.Join(dataDir, "secrets.yaml")), - dnsmasq: dnsmasq.NewConfigGenerator(dnsmasqConfigPath), + dnsmasq: dnsmasq.NewManager(dnsmasqConfigPath), haproxy: haproxy.NewManager(haproxyConfigPath), nftables: nftables.NewManager(nftablesRulesPath), ddns: ddns.NewRunner(), @@ -142,7 +142,7 @@ func (api *API) StartDDNS(ctx gocontext.Context) { func (api *API) StartDNSFilter(ctx gocontext.Context) { api.dnsFilterRunner = dnsfilter.NewRunner(api.dnsFilter, func() { if err := api.dnsmasq.ReloadService(); err != nil { - slog.Warn("dns-filter: failed to reload dnsmasq after update", "error", err) + slog.Warn("failed to reload dnsmasq after update", "component", "dns-filter", "error", err) } api.broadcastDNSFilterEvent("dns-filter:updated", "DNS filter lists updated") }) diff --git a/internal/api/v1/handlers_authelia.go b/internal/api/v1/handlers_authelia.go index d619cb6..c907d7a 100644 --- a/internal/api/v1/handlers_authelia.go +++ b/internal/api/v1/handlers_authelia.go @@ -150,26 +150,26 @@ func (api *API) enableAuthelia(state *config.State) error { } if err := api.authelia.EnsureDataDir(); err != nil { - return fmt.Errorf("Failed to create data directory: %v", err) + return fmt.Errorf("failed to create data directory: %w", err) } if err := api.ensureAutheliaSecrets(); err != nil { - return fmt.Errorf("Failed to generate secrets: %v", err) + return fmt.Errorf("failed to generate secrets: %w", err) } if err := api.authelia.EnsureJWKS(); err != nil { - return fmt.Errorf("Failed to generate JWKS: %v", err) + return fmt.Errorf("failed to generate JWKS: %w", err) } if err := api.authelia.EnsureUsersDB(); err != nil { - return fmt.Errorf("Failed to initialize user database: %v", err) + return fmt.Errorf("failed to initialize user database: %w", err) } if err := api.generateAutheliaConfig(state); err != nil { - return fmt.Errorf("Failed to generate config: %v", err) + return fmt.Errorf("failed to generate config: %w", err) } api.ensureAuthDomain(state.Cloud.Authelia.Domain) if api.authelia.UserCount() > 0 { if err := api.authelia.RestartService(); err != nil { - return fmt.Errorf("Configuration saved but Authelia failed to start: %v", err) + return fmt.Errorf("authelia failed to start: %w", err) } } return nil diff --git a/internal/api/v1/handlers_dnsfilter.go b/internal/api/v1/handlers_dnsfilter.go index a5b4b7f..842e363 100644 --- a/internal/api/v1/handlers_dnsfilter.go +++ b/internal/api/v1/handlers_dnsfilter.go @@ -83,13 +83,13 @@ func (api *API) DNSFilterUpdateConfig(w http.ResponseWriter, r *http.Request) { } api.dnsFilterRunner.Start(api.ctx, interval) - slog.Info("dns-filter: enabled", "interval", interval) + slog.Info("enabled", "component", "dns-filter", "interval", interval) } else { // Stop the runner and clear addn-hosts api.dnsFilterRunner.Stop() api.dnsmasq.SetFilterConfPath("") - slog.Info("dns-filter: disabled") + slog.Info("disabled", "component", "dns-filter") } // Reconcile to add/remove addn-hosts from dnsmasq config @@ -162,11 +162,11 @@ func (api *API) DNSFilterRemoveList(w http.ResponseWriter, r *http.Request) { // Recompile without the removed list go func() { if _, err := api.dnsFilter.Compile(); err != nil { - slog.Warn("dns-filter: recompile after removal failed", "error", err) + slog.Warn("recompile after removal failed", "component", "dns-filter", "error", err) return } if err := api.dnsmasq.ReloadService(); err != nil { - slog.Warn("dns-filter: reload after removal failed", "error", err) + slog.Warn("reload after removal failed", "component", "dns-filter", "error", err) } api.broadcastDNSFilterEvent("dns-filter:updated", "DNS filter lists updated") }() @@ -194,11 +194,11 @@ func (api *API) DNSFilterToggleList(w http.ResponseWriter, r *http.Request) { // Recompile with updated list status go func() { if _, err := api.dnsFilter.Compile(); err != nil { - slog.Warn("dns-filter: recompile after toggle failed", "error", err) + slog.Warn("recompile after toggle failed", "component", "dns-filter", "error", err) return } if err := api.dnsmasq.ReloadService(); err != nil { - slog.Warn("dns-filter: reload after toggle failed", "error", err) + slog.Warn("reload after toggle failed", "component", "dns-filter", "error", err) } api.broadcastDNSFilterEvent("dns-filter:updated", "DNS filter lists updated") }() @@ -240,11 +240,11 @@ func (api *API) DNSFilterSetCustomEntry(w http.ResponseWriter, r *http.Request) // Recompile with updated custom entries go func() { if _, err := api.dnsFilter.Compile(); err != nil { - slog.Warn("dns-filter: recompile after custom entry failed", "error", err) + slog.Warn("recompile after custom entry failed", "component", "dns-filter", "error", err) return } if err := api.dnsmasq.ReloadService(); err != nil { - slog.Warn("dns-filter: reload after custom entry failed", "error", err) + slog.Warn("reload after custom entry failed", "component", "dns-filter", "error", err) } api.broadcastDNSFilterEvent("dns-filter:updated", "DNS filter custom entry updated") }() @@ -268,11 +268,11 @@ func (api *API) DNSFilterRemoveCustomEntry(w http.ResponseWriter, r *http.Reques // Recompile go func() { if _, err := api.dnsFilter.Compile(); err != nil { - slog.Warn("dns-filter: recompile after custom removal failed", "error", err) + slog.Warn("recompile after custom removal failed", "component", "dns-filter", "error", err) return } if err := api.dnsmasq.ReloadService(); err != nil { - slog.Warn("dns-filter: reload after custom removal failed", "error", err) + slog.Warn("reload after custom removal failed", "component", "dns-filter", "error", err) } api.broadcastDNSFilterEvent("dns-filter:updated", "DNS filter custom entry removed") }() @@ -328,11 +328,11 @@ func (api *API) DNSFilterUploadList(w http.ResponseWriter, r *http.Request) { // Recompile with new list go func() { if _, err := api.dnsFilter.Compile(); err != nil { - slog.Warn("dns-filter: recompile after upload failed", "error", err) + slog.Warn("recompile after upload failed", "component", "dns-filter", "error", err) return } if err := api.dnsmasq.ReloadService(); err != nil { - slog.Warn("dns-filter: reload after upload failed", "error", err) + slog.Warn("reload after upload failed", "component", "dns-filter", "error", err) } api.broadcastDNSFilterEvent("dns-filter:updated", "DNS filter list uploaded") }() diff --git a/internal/crowdsec/manager.go b/internal/crowdsec/manager.go index 3d17513..6e0a524 100644 --- a/internal/crowdsec/manager.go +++ b/internal/crowdsec/manager.go @@ -12,8 +12,8 @@ import ( type Machine struct { MachineID string `json:"machineId"` IsValidated bool `json:"isValidated"` - LastPush string `json:"last_push,omitempty"` - LastHeartbeat string `json:"last_heartbeat,omitempty"` + LastPush string `json:"lastPush,omitempty"` + LastHeartbeat string `json:"lastHeartbeat,omitempty"` IPAddress string `json:"ipAddress,omitempty"` Version string `json:"version,omitempty"` } @@ -104,10 +104,29 @@ func (m *Manager) GetMachines() ([]Machine, error) { if out == "" || out == "null" { return []Machine{}, nil } - var machines []Machine - if err := json.Unmarshal([]byte(out), &machines); err != nil { + // cscli outputs snake_case JSON — map to our camelCase API types + var raw []struct { + MachineID string `json:"machineId"` + IsValidated bool `json:"isValidated"` + LastPush string `json:"last_push"` + LastHeartbeat string `json:"last_heartbeat"` + IPAddress string `json:"ipAddress"` + Version string `json:"version"` + } + if err := json.Unmarshal([]byte(out), &raw); err != nil { return nil, fmt.Errorf("parsing machines: %w", err) } + machines := make([]Machine, 0, len(raw)) + for _, r := range raw { + machines = append(machines, Machine{ + MachineID: r.MachineID, + IsValidated: r.IsValidated, + LastPush: r.LastPush, + LastHeartbeat: r.LastHeartbeat, + IPAddress: r.IPAddress, + Version: r.Version, + }) + } return machines, nil } diff --git a/internal/ddns/cloudflare.go b/internal/ddns/cloudflare.go index 68d2184..6927467 100644 --- a/internal/ddns/cloudflare.go +++ b/internal/ddns/cloudflare.go @@ -155,7 +155,7 @@ func (rn *Runner) checkAndUpdate() { p := rn.paramsFn() if p.APIToken == "" || len(p.Records) == 0 { - slog.Debug("DDNS: no token or records, skipping", "component", "ddns") + slog.Debug("no token or records, skipping", "component", "ddns") return } @@ -164,11 +164,11 @@ func (rn *Runner) checkAndUpdate() { rn.mu.Lock() rn.status.LastError = fmt.Sprintf("getting public IP: %v", err) rn.mu.Unlock() - slog.Error("DDNS: failed to get public IP", "component", "ddns", "error", err) + slog.Error("failed to get public IP", "component", "ddns", "error", err) return } - slog.Debug("DDNS: syncing records", "component", "ddns", "ip", ip, "records", p.Records) + slog.Debug("syncing records", "component", "ddns", "ip", ip, "records", p.Records) cf := &cfClient{apiToken: p.APIToken} @@ -177,7 +177,7 @@ func (rn *Runner) checkAndUpdate() { for _, record := range p.Records { if err := cf.updateRecord(record, ip); err != nil { lastErr = fmt.Sprintf("updating %s: %v", record, err) - slog.Error("DDNS: failed to update record", "component", "ddns", "record", record, "error", err) + slog.Error("failed to update record", "component", "ddns", "record", record, "error", err) records = append(records, RecordStatus{Name: record, OK: false, Error: err.Error()}) } else { records = append(records, RecordStatus{Name: record, IP: ip, OK: true}) @@ -304,13 +304,13 @@ func (c *cfClient) updateRecord(recordName, ip string) error { // No A record. Remove any CNAME that would conflict. if cnameID, _, cnameErr := c.getRecordID(zoneID, recordName, "CNAME"); cnameErr == nil { - slog.Info("DDNS: removing CNAME before creating A record", "component", "ddns", "record", recordName) + slog.Info("removing CNAME before creating A record", "component", "ddns", "record", recordName) if err := c.deleteRecord(zoneID, cnameID); err != nil { return fmt.Errorf("removing CNAME for %s: %w", recordName, err) } } - slog.Info("DDNS: creating A record", "component", "ddns", "record", recordName) + slog.Info("creating A record", "component", "ddns", "record", recordName) return c.createRecord(zoneID, recordName, ip) } diff --git a/internal/dnsfilter/manager.go b/internal/dnsfilter/manager.go index 40ae386..16269b5 100644 --- a/internal/dnsfilter/manager.go +++ b/internal/dnsfilter/manager.go @@ -315,7 +315,7 @@ func (m *Manager) UpdateLists(ctx context.Context) error { continue } - slog.Info("dns-filter: downloading list", "name", bl.Name, "url", bl.URL) + slog.Info("downloading list", "component", "dns-filter", "name", bl.Name, "url", bl.URL) req, err := http.NewRequestWithContext(ctx, http.MethodGet, bl.URL, nil) if err != nil { @@ -329,7 +329,7 @@ func (m *Manager) UpdateLists(ctx context.Context) error { if err != nil { bl.LastError = fmt.Sprintf("download failed: %v", err) changed = true - slog.Warn("dns-filter: download failed", "name", bl.Name, "error", err) + slog.Warn("download failed", "component", "dns-filter", "name", bl.Name, "error", err) continue } @@ -337,7 +337,7 @@ func (m *Manager) UpdateLists(ctx context.Context) error { resp.Body.Close() bl.LastError = fmt.Sprintf("HTTP %d", resp.StatusCode) changed = true - slog.Warn("dns-filter: download failed", "name", bl.Name, "status", resp.StatusCode) + slog.Warn("download failed", "component", "dns-filter", "name", bl.Name, "status", resp.StatusCode) continue } @@ -393,7 +393,7 @@ func (m *Manager) Compile() (int, error) { domains, err := ParseFile(cachePath) if err != nil { - slog.Warn("dns-filter: failed to parse list", "name", bl.Name, "error", err) + slog.Warn("failed to parse list", "component", "dns-filter", "name", bl.Name, "error", err) continue } @@ -446,7 +446,7 @@ func (m *Manager) Compile() (int, error) { // Update entry counts and save _ = m.SaveFilterData(fd) - slog.Info("dns-filter: compiled blocklist", "domains", len(sorted)) + slog.Info("compiled blocklist", "component", "dns-filter", "domains", len(sorted)) return len(sorted), nil } @@ -496,7 +496,7 @@ func (m *Manager) GetQueryStats() *QueryStats { cmd := exec.Command("sudo", "journalctl", "-u", "dnsmasq", "--since", "24 hours ago", "--no-pager", "-o", "cat") output, err := cmd.Output() if err != nil { - slog.Debug("dns-filter: failed to read dnsmasq journal", "error", err) + slog.Debug("failed to read dnsmasq journal", "component", "dns-filter", "error", err) return nil } diff --git a/internal/dnsfilter/runner.go b/internal/dnsfilter/runner.go index 4bb947a..9e9671d 100644 --- a/internal/dnsfilter/runner.go +++ b/internal/dnsfilter/runner.go @@ -75,7 +75,7 @@ func (r *Runner) Trigger() { func (r *Runner) run(ctx context.Context, intervalHours int) { interval := time.Duration(intervalHours) * time.Hour - slog.Info("dns-filter: runner started", "interval", interval) + slog.Info("runner started", "component", "dns-filter", "interval", interval) // Immediate update on start r.updateAndCompile(ctx) @@ -89,7 +89,7 @@ func (r *Runner) run(ctx context.Context, intervalHours int) { r.mu.Lock() r.running = false r.mu.Unlock() - slog.Info("dns-filter: runner stopped") + slog.Info("runner stopped", "component", "dns-filter") return case <-ticker.C: r.updateAndCompile(ctx) @@ -101,11 +101,11 @@ func (r *Runner) run(ctx context.Context, intervalHours int) { func (r *Runner) updateAndCompile(ctx context.Context) { if err := r.manager.UpdateLists(ctx); err != nil { - slog.Error("dns-filter: update failed", "error", err) + slog.Error("update failed", "component", "dns-filter", "error", err) } if _, err := r.manager.Compile(); err != nil { - slog.Error("dns-filter: compile failed", "error", err) + slog.Error("compile failed", "component", "dns-filter", "error", err) return } diff --git a/internal/dnsmasq/config.go b/internal/dnsmasq/config.go index 6a0004e..0bcafdd 100644 --- a/internal/dnsmasq/config.go +++ b/internal/dnsmasq/config.go @@ -24,36 +24,36 @@ type DNSEntry struct { Wildcard bool // true: address=/ (all subdomains); false: host-record= (exact match) } -// ConfigGenerator handles dnsmasq configuration generation -type ConfigGenerator struct { +// Manager handles dnsmasq configuration generation +type Manager struct { configPath string filterConfPath string // path to addn-hosts file for DNS filtering } -// NewConfigGenerator creates a new dnsmasq config generator -func NewConfigGenerator(configPath string) *ConfigGenerator { +// NewManager creates a new dnsmasq config generator +func NewManager(configPath string) *Manager { if configPath == "" { configPath = "/etc/dnsmasq.d/wild-cloud.conf" } - return &ConfigGenerator{ + return &Manager{ configPath: configPath, } } // GetConfigPath returns the dnsmasq config file path -func (g *ConfigGenerator) GetConfigPath() string { +func (g *Manager) GetConfigPath() string { return g.configPath } // SetFilterConfPath sets the path to an additional hosts file for DNS filtering. // When set, the generated config includes an addn-hosts= directive. -func (g *ConfigGenerator) SetFilterConfPath(path string) { +func (g *Manager) SetFilterConfPath(path string) { g.filterConfPath = path } // Generate creates a dnsmasq configuration from registered domain entries. // It auto-detects the Wild Central server's IP address for the listen directive. -func (g *ConfigGenerator) Generate(cfg *config.State, entries []DNSEntry) string { +func (g *Manager) Generate(cfg *config.State, entries []DNSEntry) string { // Use configured dnsmasq IP (bound to eth0), falling back to auto-detect. // Auto-detect can pick wlan0 if that's the default route, which is wrong // when dnsmasq is only listening on eth0. @@ -123,7 +123,7 @@ log-dhcp // A full restart is required because SIGHUP (reload) does not re-read // host-record= directives — only address=/ and a few other directives // are reloaded on SIGHUP. -func (g *ConfigGenerator) RestartService() error { +func (g *Manager) RestartService() error { cmd := exec.Command("systemctl", "restart", "dnsmasq.service") output, err := cmd.CombinedOutput() if err != nil { @@ -133,18 +133,18 @@ func (g *ConfigGenerator) RestartService() error { return nil } -// ServiceStatus represents the status of the dnsmasq service -type ServiceStatus struct { +// Status represents the status of the dnsmasq service +type Status struct { Status string `json:"status"` PID int `json:"pid"` IP string `json:"ip"` - ConfigFile string `json:"config_file"` - DomainsConfigured int `json:"domains_configured"` - LastRestart time.Time `json:"last_restart"` + ConfigFile string `json:"configFile"` + DomainsConfigured int `json:"domainsConfigured"` + LastRestart time.Time `json:"lastRestart"` } // GetStatus checks the status of the dnsmasq service -func (g *ConfigGenerator) GetStatus() (*ServiceStatus, error) { +func (g *Manager) GetStatus() (*Status, error) { // Get the Wild Central IP address dnsIP, err := network.GetWildCentralIP() if err != nil { @@ -152,7 +152,7 @@ func (g *ConfigGenerator) GetStatus() (*ServiceStatus, error) { dnsIP = "" } - status := &ServiceStatus{ + status := &Status{ ConfigFile: g.configPath, IP: dnsIP, } @@ -205,7 +205,7 @@ func (g *ConfigGenerator) GetStatus() (*ServiceStatus, error) { } // ReadConfig reads the current dnsmasq configuration -func (g *ConfigGenerator) ReadConfig() (string, error) { +func (g *Manager) ReadConfig() (string, error) { data, err := os.ReadFile(g.configPath) if err != nil { return "", fmt.Errorf("reading dnsmasq config: %w", err) @@ -214,7 +214,7 @@ func (g *ConfigGenerator) ReadConfig() (string, error) { } // UpdateConfig regenerates and writes the dnsmasq configuration and optionally restarts. -func (g *ConfigGenerator) UpdateConfig(cfg *config.State, entries []DNSEntry, restart bool) error { +func (g *Manager) UpdateConfig(cfg *config.State, entries []DNSEntry, restart bool) error { // Generate fresh config from scratch configContent := g.Generate(cfg, entries) @@ -235,7 +235,7 @@ func (g *ConfigGenerator) UpdateConfig(cfg *config.State, entries []DNSEntry, re // GenerateMainConfig creates the main dnsmasq configuration with global settings. // Used by the DnsmasqGenerate handler to preview/apply the base config independently // of domain-specific DNS entries (which are handled by Generate + UpdateConfig). -func (g *ConfigGenerator) GenerateMainConfig(cfg *config.State) string { +func (g *Manager) GenerateMainConfig(cfg *config.State) string { dnsIP, err := network.GetWildCentralIP() if err != nil { slog.Error("failed to detect Wild Central IP", "component", "dnsmasq", "error", err) @@ -300,7 +300,7 @@ log-dhcp } // ValidateConfig tests a dnsmasq configuration file for syntax errors -func (g *ConfigGenerator) ValidateConfig(configPath string) error { +func (g *Manager) ValidateConfig(configPath string) error { cmd := exec.Command("dnsmasq", "--test", "-C", configPath) output, err := cmd.CombinedOutput() if err != nil { @@ -314,13 +314,13 @@ func (g *ConfigGenerator) ValidateConfig(configPath string) error { // ReloadService restarts dnsmasq to apply configuration changes. // Uses a full restart because SIGHUP does not re-read host-record= directives. -func (g *ConfigGenerator) ReloadService() error { +func (g *Manager) ReloadService() error { return g.RestartService() } // ConfigureSystemDNS configures systemd-resolved to use the local dnsmasq server // This should only be called on first start of dnsmasq -func (g *ConfigGenerator) ConfigureSystemDNS() error { +func (g *Manager) ConfigureSystemDNS() error { // Auto-detect network info to get the DNS IP netInfo, err := network.DetectNetworkInfo() if err != nil { diff --git a/internal/dnsmasq/config_test.go b/internal/dnsmasq/config_test.go index 48e6508..bbbeaaf 100644 --- a/internal/dnsmasq/config_test.go +++ b/internal/dnsmasq/config_test.go @@ -18,7 +18,7 @@ func wildcardEntry(domain, ip string) DNSEntry { // Test: Generate with exact-match entries produces host-record= directives func TestGenerate_WithEntries(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") entries := []DNSEntry{ entry("cloud.example.com", "192.168.1.10"), entry("internal.example.com", "192.168.1.10"), @@ -39,7 +39,7 @@ func TestGenerate_WithEntries(t *testing.T) { // Test: Generate with no entries still produces valid config skeleton func TestGenerate_NoEntries(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") out := g.Generate(nil, []DNSEntry{}) @@ -56,7 +56,7 @@ func TestGenerate_NoEntries(t *testing.T) { // Test: Generate skips entries with empty IP func TestGenerate_EntryWithoutIP(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") out := g.Generate(nil, []DNSEntry{entry("cloud.example.com", "")}) @@ -67,7 +67,7 @@ func TestGenerate_EntryWithoutIP(t *testing.T) { // Test: GenerateMainConfig produces valid base config func TestGenerateMainConfig(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") out := g.GenerateMainConfig(nil) @@ -81,7 +81,7 @@ func TestGenerateMainConfig(t *testing.T) { // Test: GenerateMainConfig with DHCP disabled does not include dhcp-range func TestGenerateMainConfig_DHCPDisabled(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") cfg := &config.State{} cfg.Cloud.Dnsmasq.DHCP.Enabled = false @@ -94,7 +94,7 @@ func TestGenerateMainConfig_DHCPDisabled(t *testing.T) { // Test: GenerateMainConfig with DHCP enabled includes dhcp-range func TestGenerateMainConfig_DHCPEnabled(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") cfg := &config.State{} cfg.Cloud.Dnsmasq.DHCP.Enabled = true cfg.Cloud.Dnsmasq.DHCP.RangeStart = "192.168.8.100" @@ -114,7 +114,7 @@ func TestGenerateMainConfig_DHCPEnabled(t *testing.T) { // Test: DHCP without gateway omits dhcp-option=3 func TestGenerateMainConfig_DHCPNoGateway(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") cfg := &config.State{} cfg.Cloud.Dnsmasq.DHCP.Enabled = true cfg.Cloud.Dnsmasq.DHCP.RangeStart = "192.168.8.100" @@ -129,7 +129,7 @@ func TestGenerateMainConfig_DHCPNoGateway(t *testing.T) { // Test: DHCP explicit gateway is used func TestGenerateMainConfig_DHCPExplicitGateway(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") cfg := &config.State{} cfg.Cloud.Dnsmasq.DHCP.Enabled = true cfg.Cloud.Dnsmasq.DHCP.RangeStart = "192.168.8.100" @@ -145,7 +145,7 @@ func TestGenerateMainConfig_DHCPExplicitGateway(t *testing.T) { // Test: DHCP static leases appear in output func TestGenerateMainConfig_DHCPStaticLeases(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") cfg := &config.State{} cfg.Cloud.Dnsmasq.DHCP.Enabled = true cfg.Cloud.Dnsmasq.DHCP.RangeStart = "192.168.8.100" @@ -167,7 +167,7 @@ func TestGenerateMainConfig_DHCPStaticLeases(t *testing.T) { // Test: DHCP lease time defaults to 24h when not specified func TestGenerateMainConfig_DHCPLeaseTimeDefault(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") cfg := &config.State{} cfg.Cloud.Dnsmasq.DHCP.Enabled = true cfg.Cloud.Dnsmasq.DHCP.RangeStart = "192.168.8.100" @@ -180,17 +180,17 @@ func TestGenerateMainConfig_DHCPLeaseTimeDefault(t *testing.T) { } } -// Test: NewConfigGenerator uses provided path -func TestNewConfigGenerator_CustomPath(t *testing.T) { - g := NewConfigGenerator("/custom/path/dnsmasq.conf") +// Test: NewManager uses provided path +func TestNewManager_CustomPath(t *testing.T) { + g := NewManager("/custom/path/dnsmasq.conf") if g.GetConfigPath() != "/custom/path/dnsmasq.conf" { t.Errorf("got %q, want /custom/path/dnsmasq.conf", g.GetConfigPath()) } } -// Test: NewConfigGenerator uses default path when empty -func TestNewConfigGenerator_DefaultPath(t *testing.T) { - g := NewConfigGenerator("") +// Test: NewManager uses default path when empty +func TestNewManager_DefaultPath(t *testing.T) { + g := NewManager("") if g.GetConfigPath() != "/etc/dnsmasq.d/wild-cloud.conf" { t.Errorf("got %q, want /etc/dnsmasq.d/wild-cloud.conf", g.GetConfigPath()) } @@ -198,7 +198,7 @@ func TestNewConfigGenerator_DefaultPath(t *testing.T) { // Test: Exact-match domain produces host-record= func TestGenerate_ExactMatchDomain(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") globalCfg := &config.State{} out := g.Generate(globalCfg, []DNSEntry{entry("my-api.payne.io", "192.168.8.151")}) @@ -216,7 +216,7 @@ func TestGenerate_ExactMatchDomain(t *testing.T) { // Test: Wildcard domain produces address=/ without local=/ func TestGenerate_WildcardDomain(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") globalCfg := &config.State{} out := g.Generate(globalCfg, []DNSEntry{wildcardEntry("cloud.payne.io", "192.168.8.240")}) @@ -234,7 +234,7 @@ func TestGenerate_WildcardDomain(t *testing.T) { // Test: No local=/ directives are ever generated func TestGenerate_NoLocalDirectives(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") globalCfg := &config.State{} entries := []DNSEntry{ @@ -251,7 +251,7 @@ func TestGenerate_NoLocalDirectives(t *testing.T) { // Test: filter-AAAA appears in generated config func TestGenerate_FilterAAAA(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") out := g.Generate(nil, nil) @@ -262,7 +262,7 @@ func TestGenerate_FilterAAAA(t *testing.T) { // Test: Mix of exact and wildcard domains func TestGenerate_MixedDomains(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") globalCfg := &config.State{} entries := []DNSEntry{ @@ -289,7 +289,7 @@ func TestGenerate_MixedDomains(t *testing.T) { // Test: empty entries list produces base config with no DNS entries func TestGenerate_EmptyEntries(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") globalCfg := &config.State{} out := g.Generate(globalCfg, []DNSEntry{}) @@ -310,7 +310,7 @@ func TestGenerate_EmptyEntries(t *testing.T) { // Test: multiple exact-match entries each get their own DNS records func TestGenerate_MultipleEntries(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") globalCfg := &config.State{} entries := []DNSEntry{ @@ -329,7 +329,7 @@ func TestGenerate_MultipleEntries(t *testing.T) { } func TestGenerate_ConfFile(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") g.SetFilterConfPath("/var/lib/wild-central/dns-filter/hosts.blocked") out := g.Generate(nil, nil) @@ -343,7 +343,7 @@ func TestGenerate_ConfFile(t *testing.T) { } func TestGenerate_NoConfFileWhenEmpty(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") // addnHostsPath is empty by default out := g.Generate(nil, nil) @@ -354,7 +354,7 @@ func TestGenerate_NoConfFileWhenEmpty(t *testing.T) { } func TestGenerateMainConfig_ConfFile(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") g.SetFilterConfPath("/tmp/hosts.blocked") cfg := &config.State{} @@ -367,7 +367,7 @@ func TestGenerateMainConfig_ConfFile(t *testing.T) { // Test: nil config doesn't panic, uses auto-detect for IP func TestGenerate_NilConfig(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") out := g.Generate(nil, []DNSEntry{}) @@ -381,7 +381,7 @@ func TestGenerate_NilConfig(t *testing.T) { // Test: when cfg.Cloud.Dnsmasq.IP is set, uses that instead of auto-detect func TestGenerate_ConfiguredDnsmasqIP(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") cfg := &config.State{} cfg.Cloud.Dnsmasq.IP = "192.168.8.100" @@ -394,7 +394,7 @@ func TestGenerate_ConfiguredDnsmasqIP(t *testing.T) { // Test: verify no leaked empty entries appear func TestGenerate_NoLeakedEmptyEntries(t *testing.T) { - g := NewConfigGenerator("/tmp/test-dnsmasq.conf") + g := NewManager("/tmp/test-dnsmasq.conf") cfg := &config.State{} cases := []struct { diff --git a/internal/haproxy/config.go b/internal/haproxy/config.go index 64d0332..54e2eea 100644 --- a/internal/haproxy/config.go +++ b/internal/haproxy/config.go @@ -572,8 +572,8 @@ func (m *Manager) RestartService() error { return nil } -// ServiceStatus represents the status of the HAProxy service -type ServiceStatus struct { +// Status represents the status of the HAProxy service +type Status struct { Status string `json:"status"` PID int `json:"pid"` ConfigFile string `json:"configFile"` @@ -581,8 +581,8 @@ type ServiceStatus struct { } // GetStatus returns the current HAProxy service status -func (m *Manager) GetStatus() (*ServiceStatus, error) { - status := &ServiceStatus{ +func (m *Manager) GetStatus() (*Status, error) { + status := &Status{ ConfigFile: m.configPath, } diff --git a/internal/network/detect.go b/internal/network/detect.go index a1771b3..4f4d732 100644 --- a/internal/network/detect.go +++ b/internal/network/detect.go @@ -9,8 +9,8 @@ import ( // NetworkInfo holds detected network configuration type NetworkInfo struct { - PrimaryIP string `json:"primary_ip"` - PrimaryInterface string `json:"primary_interface"` + PrimaryIP string `json:"primaryIP"` + PrimaryInterface string `json:"primaryInterface"` Gateway string `json:"gateway"` } diff --git a/internal/reconcile/reconciler.go b/internal/reconcile/reconciler.go index 2940ddf..8326848 100644 --- a/internal/reconcile/reconciler.go +++ b/internal/reconcile/reconciler.go @@ -29,11 +29,11 @@ type HAProxyManager interface { ReloadService() error } -// DNSManager is the subset of dnsmasq.ConfigGenerator used by reconciliation. +// DNSManager is the subset of dnsmasq.Manager used by reconciliation. type DNSManager interface { UpdateConfig(cfg *config.State, entries []dnsmasq.DNSEntry, restart bool) error SetFilterConfPath(path string) - GetStatus() (*dnsmasq.ServiceStatus, error) + GetStatus() (*dnsmasq.Status, error) } // DomainManager is the subset of domains.Manager used by reconciliation. @@ -85,13 +85,13 @@ type Reconciler struct { func (r *Reconciler) Reconcile() { doms, err := r.Domains.List() if err != nil { - slog.Error("reconcile: failed to list domains", "error", err) + slog.Error("failed to list domains", "component", "reconcile", "error", err) return } globalCfg, err := config.LoadState(r.StatePath) if err != nil { - slog.Warn("reconcile: failed to load state, using empty", "error", err) + slog.Warn("failed to load state, using empty", "component", "reconcile", "error", err) globalCfg = &config.State{} } @@ -105,7 +105,7 @@ func (r *Reconciler) Reconcile() { if hasCertForDomain(route.Domain) { activeHTTPRoutes = append(activeHTTPRoutes, route) } else { - slog.Warn("reconcile: skipping L7 route (no cert)", "domain", route.Domain) + slog.Warn("skipping L7 route (no cert)", "component", "reconcile", "domain", route.Domain) } } @@ -132,7 +132,7 @@ func (r *Reconciler) Reconcile() { } if err := r.DNS.UpdateConfig(globalCfg, dnsEntries, true); err != nil { - slog.Error("reconcile: failed to update dnsmasq", "error", err) + slog.Error("failed to update dnsmasq", "component", "reconcile", "error", err) } else { r.broadcastDNSEvent("dnsmasq:config", "DNS config regenerated from registered domains") } @@ -143,7 +143,7 @@ func (r *Reconciler) Reconcile() { r.DDNS.Trigger() - slog.Info("reconcile: networking updated", + slog.Info("networking updated", "component", "reconcile", "domains", len(doms), "l4Routes", len(l4Routes), "l7Routes", len(httpRoutes), @@ -235,7 +235,7 @@ func (r *Reconciler) writeHAProxyConfig(l4Routes []haproxy.L4Route, httpRoutes [ if r.GenerateAutheliaConfig != nil { if err := r.GenerateAutheliaConfig(globalCfg); err != nil { - slog.Warn("reconcile: failed to regenerate authelia config", "error", err) + slog.Warn("failed to regenerate authelia config", "component", "reconcile", "error", err) } else if r.Auth.UserCount() > 0 { _ = r.Auth.RestartService() } @@ -247,8 +247,8 @@ func (r *Reconciler) writeHAProxyConfig(l4Routes []haproxy.L4Route, httpRoutes [ if err := r.HAProxy.WriteConfig(cfg); err != nil { brokenDomains := haproxy.FindBrokenServices(cfg, haproxy.ParseValidationErrors(err.Error())) if len(brokenDomains) > 0 { - slog.Error("reconcile: excluding broken domains and retrying", - "broken", brokenDomains, "error", err) + slog.Error("excluding broken domains and retrying", + "component", "reconcile", "broken", brokenDomains, "error", err) exclude := map[string]bool{} for _, d := range brokenDomains { @@ -271,17 +271,17 @@ func (r *Reconciler) writeHAProxyConfig(l4Routes []haproxy.L4Route, httpRoutes [ genOpts.HTTPRoutes = filteredHTTP cfg = r.HAProxy.GenerateWithOpts(filteredL4, nil, genOpts) if err := r.HAProxy.WriteConfig(cfg); err != nil { - slog.Error("reconcile: retry also failed", "error", err) + slog.Error("retry also failed", "component", "reconcile", "error", err) } else if err := r.HAProxy.ReloadService(); err != nil { - slog.Warn("reconcile: failed to reload HAProxy", "error", err) + slog.Warn("failed to reload HAProxy", "component", "reconcile", "error", err) } else { r.broadcastEvent("haproxy:config", "HAProxy config regenerated (excluded broken domains)") } } else { - slog.Error("reconcile: failed to write HAProxy config (no broken domains identified)", "error", err) + slog.Error("failed to write HAProxy config (no broken domains identified)", "component", "reconcile", "error", err) } } else if err := r.HAProxy.ReloadService(); err != nil { - slog.Warn("reconcile: failed to reload HAProxy", "error", err) + slog.Warn("failed to reload HAProxy", "component", "reconcile", "error", err) } else { r.broadcastEvent("haproxy:config", "HAProxy config regenerated from registered domains") } @@ -296,7 +296,7 @@ func cleanZeroByteCerts(certsDir string) { for _, e := range entries { if strings.HasSuffix(e.Name(), ".pem") { if info, err := e.Info(); err == nil && info.Size() == 0 { - slog.Warn("reconcile: removing 0-byte cert file", "file", e.Name()) + slog.Warn("removing 0-byte cert file", "component", "reconcile", "file", e.Name()) _ = os.Remove(filepath.Join(certsDir, e.Name())) } } @@ -355,7 +355,7 @@ func ensureTLSCerts(globalCfg *config.State, doms []domains.Domain) { if gatewayDomain != "" && strings.HasSuffix(dom.DomainName, "."+gatewayDomain) { wildcardCert := certbot.HAProxyCertPath(gatewayDomain) if _, err := os.Stat(wildcardCert); err != nil { - slog.Warn("reconcile/tls: no wildcard cert for gateway domain — provision via Certificates page", + slog.Warn("no wildcard cert for gateway domain — provision via Certificates page", "component", "reconcile", "domain", dom.DomainName, "needed", "*."+gatewayDomain) } continue @@ -363,7 +363,7 @@ func ensureTLSCerts(globalCfg *config.State, doms []domains.Domain) { // Check individual cert if _, err := os.Stat(certbot.HAProxyCertPath(dom.DomainName)); err != nil { - slog.Warn("reconcile/tls: no cert for domain — provision via Certificates page", + slog.Warn("no cert for domain — provision via Certificates page", "component", "reconcile", "domain", dom.DomainName) } } diff --git a/internal/reconcile/reconciler_test.go b/internal/reconcile/reconciler_test.go index 893a25b..bbb05be 100644 --- a/internal/reconcile/reconciler_test.go +++ b/internal/reconcile/reconciler_test.go @@ -51,7 +51,7 @@ func (s *stubDNS) UpdateConfig(_ *config.State, entries []dnsmasq.DNSEntry, _ bo return nil } func (s *stubDNS) SetFilterConfPath(p string) { s.filterPath = p } -func (s *stubDNS) GetStatus() (*dnsmasq.ServiceStatus, error) { return nil, nil } +func (s *stubDNS) GetStatus() (*dnsmasq.Status, error) { return nil, nil } type stubAuth struct { userCount int