diff --git a/CHANGELOG.md b/CHANGELOG.md index d6e8a319..e62f3702 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,6 +79,7 @@ removed on this schedule: #### Configuration - `derp.paths` files must end in `.yaml`, `.yml`, `.json` or `.hujson`; the extension picks the format +- `dns.extra_records_path` must end in `.json`, `.hujson`, `.yaml` or `.yml`; the extension picks the format - A `derp.paths` file that decodes to no regions now stops headscale from starting instead of being silently ignored #### CLI @@ -94,6 +95,7 @@ removed on this schedule: - Deleting a user that still owns nodes now lists the nodes (ID and hostname) that must be deleted first [#3475](https://github.com/juanfont/headscale/pull/3475) - Headscale now requires Go 1.27 to build - `derp.paths` files may be Tailscale JSON or HuJSON DERP maps as well as YAML +- `dns.extra_records_path` files may be HuJSON or YAML as well as JSON - A `derp.paths` region set to `null` removes that region again, as documented ## 0.29.4 (2026-09-23) diff --git a/config-example.yaml b/config-example.yaml index 0e3bdf9f..685e22dd 100644 --- a/config-example.yaml +++ b/config-example.yaml @@ -353,7 +353,8 @@ dns: # # you can also put it in one line # - { name: "prometheus.myvpn.example.com", type: "A", value: "100.64.0.3" } # - # Alternatively, extra DNS records can be loaded from a JSON file. + # Alternatively, extra DNS records can be loaded from a file. The extension + # picks the format: .json, .hujson, .yaml or .yml. # Headscale processes this file on each change. # extra_records_path: /var/lib/headscale/extra-records.json diff --git a/docs/ref/dns.md b/docs/ref/dns.md index fe1862ef..af080a42 100644 --- a/docs/ref/dns.md +++ b/docs/ref/dns.md @@ -67,7 +67,7 @@ hostname and port combination "http://hostname-in-magic-dns.myvpn.example.com:30 !!! tip "Good to know" - The `dns.extra_records_path` option in the [configuration file](configuration.md) needs to reference the - JSON file containing extra DNS records. + file containing extra DNS records. Its extension picks the format: `.json`, `.hujson`, `.yaml` or `.yml`. - Be sure to "sort keys" and produce a stable output in case you generate the JSON file with a script. Headscale uses a checksum to detect changes to the file and a stable output avoids unnecessary processing. diff --git a/hscontrol/dns/extrarecords.go b/hscontrol/dns/extrarecords.go index 29d2430a..010bed13 100644 --- a/hscontrol/dns/extrarecords.go +++ b/hscontrol/dns/extrarecords.go @@ -3,7 +3,6 @@ package dns import ( "context" "crypto/sha256" - "encoding/json" "errors" "fmt" "os" @@ -12,6 +11,7 @@ import ( "github.com/cenkalti/backoff/v5" "github.com/fsnotify/fsnotify" + "github.com/juanfont/headscale/hscontrol/util" "github.com/rs/zerolog/log" "tailscale.com/tailcfg" "tailscale.com/util/set" @@ -222,8 +222,9 @@ func (e *ExtraRecordsMan) updateRecords() { } } -// readExtraRecordsFromPath reads a JSON file of [tailcfg.DNSRecord] -// and returns the records and the hash of the file. +// readExtraRecordsFromPath reads a file of [tailcfg.DNSRecord] in the format its +// extension names (see [util.UnmarshalByExt]) and returns the records and the +// hash of the file. func readExtraRecordsFromPath(path string) ([]tailcfg.DNSRecord, [32]byte, error) { var zero [32]byte @@ -238,14 +239,13 @@ func readExtraRecordsFromPath(path string) ([]tailcfg.DNSRecord, [32]byte, error return nil, zero, nil } - var records []tailcfg.DNSRecord + // Hash first: decoding HuJSON may rewrite comments in b. + hash := sha256.Sum256(b) - err = json.Unmarshal(b, &records) + records, err := util.UnmarshalByExt[[]tailcfg.DNSRecord](path, b) if err != nil { return nil, zero, fmt.Errorf("unmarshalling records, content: %q: %w", string(b), err) } - hash := sha256.Sum256(b) - return records, hash, nil }