From 2efd9c4944cd425538d52a5c5f56c45219151363 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 7 Jun 2026 21:59:01 +0200 Subject: [PATCH] fix(matrix): write a static resolv.conf for tuwunel (eval-proven; #1500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #1485's simplified fix turned off useHostResolvConf and trusted resolvconf to honour networking.nameservers, but that is a runtime resolvconf behaviour we couldn't verify at eval time — and it STILL came up with an empty /etc/resolv.conf in practice, so tuwunel kept failing the resolver init and matrix stayed down (#1500). Take resolvconf out of the loop entirely: resolvconf.enable = false plus an explicit environment.etc."resolv.conf" that writes nameserver statically. Nothing regenerates it out from under tuwunel. Eval-proven (unlike the prior variant): on a host with matrix+network on, the generated container environment.etc."resolv.conf".text is "nameserver \noptions edns0\n". --- nix/modules/hive-matrix.nix | 41 ++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/nix/modules/hive-matrix.nix b/nix/modules/hive-matrix.nix index aef3ec2c..4e620d79 100644 --- a/nix/modules/hive-matrix.nix +++ b/nix/modules/hive-matrix.nix @@ -342,25 +342,28 @@ in # tuwunel hard-fails to boot if `/etc/resolv.conf` has no # `nameserver` line (`Failed to configure DNS resolver ... no # nameservers found in config` → exit 1). This declarative - # nixos-container had it come up EMPTY (just `options edns0`) - # even with `networking.nameservers` set: the nixos-container - # default `useHostResolvConf = true` puts in-container resolvconf - # in host-tracking mode, which ignores `networking.nameservers` - # and never receives the host's resolv.conf across the - # shared-netns boundary — so resolvconf regenerates an empty file - # and tuwunel dies at boot. + # nixos-container comes up with an EMPTY resolv.conf even with + # `networking.nameservers` set: the nixos-container default + # `useHostResolvConf = true` puts in-container resolvconf in + # host-tracking mode (ignores `networking.nameservers`, and never + # gets the host file across the shared-netns boundary), so it + # regenerates an empty file and tuwunel dies at boot. # - # When the hive network module is on, turn off host-tracking so - # resolvconf honours `networking.nameservers`, pointing the - # resolver at the dnsmasq the network module runs at `bridgeIp`. + # The earlier fix turned host-tracking off and trusted resolvconf + # to honour `networking.nameservers` — but that's a RUNTIME + # resolvconf behaviour, not verifiable at eval time, and it STILL + # came up empty in practice (#1500). So take resolvconf out of the + # loop entirely and write a STATIC `/etc/resolv.conf` from + # `bridgeIp` that nothing regenerates. Eval-proven: the generated + # `environment.etc."resolv.conf".text` is `nameserver `. # This container always shares the host netns - # (`privateNetwork = false`), so it reaches `bridgeIp` whether or - # not `isolateContainers` is set. With the network module off, - # inherit the host's resolv.conf (which carries the host - # resolver). See `docs/network.md`. + # (`privateNetwork = false`), so it reaches `bridgeIp` regardless + # of `isolateContainers`. Network module off → inherit the host's + # resolv.conf. See `docs/network.md`. networking = lib.mkMerge [ (lib.mkIf networkCfg.enable { useHostResolvConf = lib.mkForce false; + resolvconf.enable = lib.mkForce false; nameservers = [ networkCfg.bridgeIp ]; }) (lib.mkIf (!networkCfg.enable) { @@ -368,6 +371,16 @@ in }) ]; + # resolvconf is disabled above, so write the static resolver file + # explicitly — NixOS won't synthesise one from `nameservers` once + # resolvconf is off, and this is the file tuwunel parses at boot. + environment.etc = lib.mkIf networkCfg.enable { + "resolv.conf".text = '' + nameserver ${networkCfg.bridgeIp} + options edns0 + ''; + }; + services.matrix-tuwunel = { enable = true; package = cfg.package;