Update binary name, usage strings, generated file headers, tests, and README. The generated BIND config header now includes the install path: go install go.askask.com/catz@latest
89 lines
2.4 KiB
Go
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 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
|
|
}
|