package main import ( "fmt" "os" "sort" "strings" ) // generateBindConf produces a BIND domains.conf from all zone entries. // Zones are sorted alphabetically. Each zone block uses 8-space indentation. // DNSSEC zones get dnssec-policy and inline-signing directives on the file line. // Zones in catalogs with also-notify configured get an also-notify directive. func generateBindConf(entries []ZoneEntry, cfg *Config) string { sorted := make([]ZoneEntry, len(entries)) copy(sorted, entries) sort.Slice(sorted, func(i, j int) bool { return sorted[i].Zone < sorted[j].Zone }) var b strings.Builder b.WriteString("# THIS FILE IS GENERATED BY catz (go install go.askask.com/catz@latest)\n") b.WriteString("#=============================================\n") b.WriteString("#\n") for _, entry := range sorted { if entry.ZoneFile == "" { continue // catalog-only zone, no BIND config needed } // Strip trailing dot for BIND zone name zoneName := strings.TrimSuffix(entry.Zone, ".") fmt.Fprintf(&b, "zone \"%s\" {\n", zoneName) b.WriteString(" type master;\n") fileLine := fmt.Sprintf(" file \"%s\";", entry.ZoneFile) if entry.DNSSEC { fileLine += " dnssec-policy standard; inline-signing yes;" } b.WriteString(fileLine + "\n") // Collect also-notify IPs from all catalogs this zone belongs to ips := alsoNotifyIPs(entry.Catalogs, cfg) if len(ips) > 0 { b.WriteString(" also-notify {") for _, ip := range ips { fmt.Fprintf(&b, " %s;", ip) } b.WriteString(" };\n") } b.WriteString("};\n") } return b.String() } // alsoNotifyIPs returns the deduplicated, sorted list of also-notify IPs // for a zone based on its catalog memberships. func alsoNotifyIPs(catalogs []string, cfg *Config) []string { if len(cfg.BindConf.AlsoNotify) == 0 { return nil } seen := make(map[string]bool) var ips []string for _, catName := range catalogs { for _, ip := range cfg.BindConf.AlsoNotify[catName] { if !seen[ip] { seen[ip] = true ips = append(ips, ip) } } } sort.Strings(ips) return ips } // writeBindConf writes the BIND config to path. // Zones without a ZoneFile are skipped (catalog-only zones). func writeBindConf(path string, entries []ZoneEntry, cfg *Config) error { content := generateBindConf(entries, cfg) if err := os.WriteFile(path, []byte(content), 0o644); err != nil { return fmt.Errorf("writing bind config %s: %w", path, err) } return nil }