[Bug] Headscale reads extra DNS records from path too early
Is this a support request?
- [x] This is not a support request
Is there an existing issue for this?
- [x] I have searched the existing issues
Current Behavior
I use the dns.extra_records_path configuration option to load DNS records from a JSON file. This file is created by a script that combines multiple files into one. The relevant line is:
jq --sort-keys --slurp 'unique | flatten' dns.d/*.json > /var/lib/headscale/extra-records.json
There are about 150 DNS records written to /var/lib/headscale/extra-records.json with each invocation. From time to time Headscale's filewatcher picks up the file while it is being written. Presumably, the output buffer is flushed by jq and a partial JSON file is persisted for a short while. This causes errors about broken JSON in Headscale's logs:
[snipped]
{\\n \\\"name\\\": \\\"acgmw.example.com\\\",\\n \\\"type\\\": \\\"A\\\",\\n \\\"value\\\": \\\"100.64.0.1\\\"\\n },\\n {\\n \\\"name\\\": \\\"acgmx.example.com\\\",\\n \\\"type\\\": \\\"A\\\",\\n \\\"value\\\": \\\"100.64.0.1\\\"\\n },\\n {\\n \\\"name\\\": \\\"acgmy.example.com\\\",\\n \\\"type\\\": \\\"A\\\",\\n \\\"v\":
unexpected end of JSON input"
As a workaround, I use stdbuf to enlarge the output buffer for jq:
stdbuf -o 1M jq --sort-keys --slurp 'unique | flatten' dns.d/*.json > /var/lib/headscale/extra-records.json
Is there a way to configure the filewatcher such that it picks up the file once no other process is writing to it?
Expected Behavior
Unsure what is the best solution to this, some ideas:
- Read the records in
dns.extra_records_pathonce no other process writes to it - Delay the read a bit (probably just a workaround)
Steps To Reproduce
- Setup Headscale 0.26.1 with
dns.extra_records_path - Create some sample JSON files
- Update the JSON file with the DNS records:
jq --sort-keys --slurp 'unique | flatten' sample-*.json > .headscale/extra-records.json - Observe the error message in Headscale's log
Environment
- OS: Debian 13
- Headscale version: 0.26.1
- Tailscale version: -
Runtime environment
- [ ] Headscale is behind a (reverse) proxy
- [ ] Headscale runs in a container
Debug information
It would probably be safer to first generate a temporary file, and then replace the existing one with the new one, in one go (using mv, which should be atomic).
(using mv, which should be atomic).
I saw some errors with mv also; but can't remember if they were identical.