Files
catz/bindconf.go
Ask Bjørn Hansen 9ff9abeabd Add also-notify support for BIND domains.conf generation
Extend catz.yaml config with a bind-conf section mapping catalog names
to also-notify IP lists. Zones in catalogs with also-notify configured
get an also-notify directive in the generated domains.conf. IPs are
deduplicated and sorted when a zone belongs to multiple catalogs.
2026-03-28 14:57:37 -07:00

89 lines
2.4 KiB
Go

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 catalog-zone-gen\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
}