# catalog-zone-gen Generate RFC 9432 DNS catalog zone files from a declarative input file. Given a list of zones with their catalog assignments and optional properties, this tool produces one BIND-format zone file per catalog. Output is deterministic: re-running with unchanged input produces unchanged output (no serial bump, no file write). ## Installation ``` go install catalog-zone-gen@latest ``` Or build from source: ``` go build -o catalog-zone-gen . ``` ## Usage ``` catalog-zone-gen [--config path] [--output-dir path] ``` **Flags:** - `--config` — path to YAML config file (default: `catz.yaml` next to the input file) - `--output-dir` — directory for output zone files (default: same directory as the input file) ## Configuration File The config file (default `catz.yaml`) defines catalog zone names and SOA parameters. ```yaml catalogs: catalog1: zone: catalog1.example.com. catalog2: zone: catalog2.example.com. soa: mname: ns1.example.com. rname: hostmaster.example.com. ``` ### Fields | Field | Required | Description | |-------|----------|-------------| | `catalogs` | yes | Map of catalog names to their zone FQDNs. Names are used as references in the input file. | | `catalogs..zone` | yes | The FQDN of the catalog zone (trailing dot optional, will be normalized). | | `soa.mname` | yes | Primary nameserver for the SOA record. | | `soa.rname` | yes | Responsible person email (in DNS format: `hostmaster.example.com.`). | SOA timing values are hardcoded: refresh=900, retry=600, expire=2147483646, minimum=0. The NS record is hardcoded to `invalid.` per RFC 9432. ## Input File Format Whitespace and comma delimited. Lines starting with `#` are comments. Blank lines are ignored. ``` [, ...] [, group=] [, coo=] ``` ### Fields | Position | Format | Description | |----------|--------|-------------| | First token | FQDN | Zone name (trailing dot optional, normalized internally). | | Bare names | identifier | Catalog assignments — must match a key in the config `catalogs` map. At least one required. | | `group=` | key=value | RFC 9432 group property. Tells consumers to apply shared configuration to grouped zones. | | `coo=` | key=value | RFC 9432 change-of-ownership property. Points to the old catalog zone during migration. | A zone can appear in multiple catalogs (for distributing to different server groups). ### Example ``` # Production zones zone.example.org catalog1, catalog2 zone.example.com catalog2, coo=old-catalog.example.com. test.example.net catalog1, group=internal app.example.org catalog1, group=external, coo=migrated.example.com. ``` Whitespace and comma placement is flexible. These are all equivalent: ``` zone.example.org catalog1,catalog2 zone.example.org catalog1 , catalog2 zone.example.org catalog1, catalog2 ``` ## Output One BIND-format zone file per catalog, written to the output directory. **Filename:** `.zone` (e.g., `catalog1.example.com.zone`) **Example output:** ``` catalog1.example.com. 0 IN SOA ns1.example.com. hostmaster.example.com. 2026030201 900 600 2147483646 0 catalog1.example.com. 0 IN NS invalid. version.catalog1.example.com. 0 IN TXT "2" grfen8g.zones.catalog1.example.com. 0 IN PTR app.example.org. group.grfen8g.zones.catalog1.example.com. 0 IN TXT "external" coo.grfen8g.zones.catalog1.example.com. 0 IN PTR migrated.example.com. 2qvgcfg.zones.catalog1.example.com. 0 IN PTR test.example.net. group.2qvgcfg.zones.catalog1.example.com. 0 IN TXT "internal" 1860l9o.zones.catalog1.example.com. 0 IN PTR zone.example.org. ``` ### Record order 1. SOA record 2. NS record (`invalid.`) 3. Version TXT record (`"2"`) 4. Member zones sorted alphabetically by zone name, each with: - PTR record (member zone) - Group TXT record (if `group=` set) - COO PTR record (if `coo=` set) All records use TTL 0 and class IN. Fully-qualified owner names; no `$ORIGIN` or `$TTL` directives. ### Member zone hashing Member zone labels are generated by FNV-1a 32-bit hashing the normalized zone name, then encoding as lowercase base32hex without padding. This produces compact labels like `grfen8g`. Changing tools will likely produce different hash labels, which is intentional per RFC 9432 Section 5.4 — it triggers a reconfig event on consumers. ### SOA serial Format: `YYYYMMDDNN` where NN is a sequence number (01-99). - New zone files start at `YYYYMMDD01`. - On subsequent runs, the tool generates output with the existing serial and compares bytes. If unchanged, no write occurs. - If content differs, the serial is incremented (same date bumps NN; new date resets to `YYYYMMDD01`). - If NN reaches 99 on the same date: the tool errors. ## Validation The tool validates input and reports errors with file location: ``` error: zones.txt:3: unknown catalog "bogus" error: zones.txt:5: zone example.com. already assigned to catalog "catalog1" at line 2 ``` **Checked conditions:** - Unknown catalog name (not in config) — error - Same zone assigned to the same catalog more than once — error - Hash collision (two zone names produce the same hash within a catalog) — error - Missing required config fields — error - Unknown properties (anything other than `group` and `coo`) — error