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. func generateBindConf(entries []ZoneEntry) 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 catalog-zone-gen\n") b.WriteString("#=============================================\n") b.WriteString("#\n") for _, entry := range sorted { // 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") b.WriteString("};\n") } return b.String() } // validateBindConf checks that every zone entry has a non-empty ZoneFile. func validateBindConf(entries []ZoneEntry) error { for _, entry := range entries { if entry.ZoneFile == "" { return fmt.Errorf("%s:%d: zone %s missing file= property (required for --bind-conf)", entry.ZonesFile, entry.Line, entry.Zone) } } return nil } // writeBindConf validates entries and writes the BIND config to path. func writeBindConf(path string, entries []ZoneEntry) error { if err := validateBindConf(entries); err != nil { return err } content := generateBindConf(entries) if err := os.WriteFile(path, []byte(content), 0o644); err != nil { return fmt.Errorf("writing bind config %s: %w", path, err) } return nil }