fix: DNS first-install — split-horizon zone creation + CoreDNS inode bind-mount
VPN clients got dns_probe_finished_bad_config / couldn't resolve any domain after first setup because: 1. complete_setup() never wrote the split-horizon DNS zone for non-LAN modes; SetupManager now accepts network_manager as an optional 3rd constructor param, and complete_setup() calls self.network_manager.update_split_horizon_zone(effective_domain, wg_ip, primary_domain) for pic_ngo/cell_to_cell modes. 2. generate_corefile() used a tmp-file + os.replace pattern; the Corefile is a Docker FILE bind-mount, so os.replace orphaned the inode and CoreDNS never saw config updates. Fixed by truncating and rewriting in place (open with 'w', seek(0), truncate()), preserving the inode CoreDNS holds. api/managers.py passes network_manager into SetupManager. Tests: new mock_network_manager fixture, 2 setup-zone tests, 1 inode regression test in test_firewall_manager.py. Verified live on pic1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -797,12 +797,18 @@ def generate_corefile(peers: List[Dict[str, Any]], corefile_path: str = COREFILE
|
||||
# local.{domain} block intentionally omitted: /data/local.zone does not exist
|
||||
# and CoreDNS logs errors on every reload for a missing zone file.
|
||||
os.makedirs(os.path.dirname(corefile_path), exist_ok=True)
|
||||
tmp_path = corefile_path + '.tmp'
|
||||
with open(tmp_path, 'w') as f:
|
||||
# Write in place (truncate + rewrite the SAME inode) rather than
|
||||
# writing a temp file and os.replace()-ing it in. The Corefile is a
|
||||
# Docker FILE bind-mount (./config/dns/Corefile:/etc/coredns/Corefile);
|
||||
# os.replace creates a NEW inode, but the container stays bound to the
|
||||
# original inode and never sees the update — so CoreDNS silently runs
|
||||
# stale config until the container restarts. CoreDNS only re-reads on
|
||||
# the SIGUSR1 we send right after this completes, so a non-atomic
|
||||
# write is safe here.
|
||||
with open(corefile_path, 'w') as f:
|
||||
f.write(corefile)
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
os.replace(tmp_path, corefile_path)
|
||||
|
||||
logger.info(f"Wrote Corefile to {corefile_path}")
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user