Standardize codebase consistency: naming, JSON tags, logging, error wrapping

- JSON tags: fix snake_case to camelCase in dnsmasq (configFile, domainsConfigured,
  lastRestart), crowdsec Machine (lastPush, lastHeartbeat), network (primaryIP,
  primaryInterface). cscli raw parsing structs keep snake_case to match CLI output.
- Error wrapping: fix %v to %w in enableAuthelia for proper error chain preservation
- Naming: rename dnsmasq.ConfigGenerator to dnsmasq.Manager (matches all other packages),
  rename ServiceStatus to Status in dnsmasq and haproxy (matches authelia, crowdsec, etc.)
- Logging: standardize all slog calls to use "component" key instead of message prefixes.
  Affects reconcile, dnsfilter, ddns — now consistent with dnsmasq, haproxy, nftables, sse.
This commit is contained in:
2026-07-14 04:38:48 +00:00
parent 428d47f876
commit 3172e56288
13 changed files with 135 additions and 116 deletions

View File

@@ -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")
})

View File

@@ -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

View File

@@ -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")
}()

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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,
}

View File

@@ -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"`
}

View File

@@ -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)
}
}

View File

@@ -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