diff --git a/.agents/skills/nix-config-debug/SKILL.md b/.agents/skills/nix-config-debug/SKILL.md index 380e4786..3cf93e73 100644 --- a/.agents/skills/nix-config-debug/SKILL.md +++ b/.agents/skills/nix-config-debug/SKILL.md @@ -24,17 +24,18 @@ bump, and the commands that destroy rollback points are in the `nix-config-updat ## 1. Localize by layer -| Layer | Symptom | Look with | -| ------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------- | -| eval | `just test` prints `false`, an eval error | `just eval-host ` (already passes `--show-trace`) | -| build | build error, hash mismatch, "marked as broken" | `just build-host `, then `nix log ` | -| activation | the deploy fails after building | the deploy output; `journalctl -u home-manager-$USER -b` for Home Manager | -| runtime | a unit is failed or restarting | `just list-failed`, `systemctl status `, `journalctl -u -b` | -| boot | errors at boot, wrong kernel | `journalctl -b -p err`; `just history` for what is booted | -| session | desktop or app misbehaves | `journalctl --user -b -p err`; the `nix-config-desktop` skill | -| remote host | anything on a Colmena host | `ssh root@ journalctl -b -p err`, same commands over SSH | -| MicroVM guest | guest down or unreachable | on the VM host: `systemctl status microvm@` and `microvm-tap-interfaces@`; then `br0` | -| secrets | missing or unreadable `/etc/agenix/*` | the `nix-config-secrets` skill | +| Layer | Symptom | Look with | +| ------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| eval | `just test` prints `false`, an eval error | `just eval-host ` (already passes `--show-trace`) | +| build | build error, hash mismatch, "marked as broken" | `just build-host `, then `nix log ` | +| activation | the deploy fails after building | the deploy output; `journalctl -u home-manager-$USER -b` for Home Manager | +| runtime | a unit is failed or restarting | `just list-failed`, `systemctl status `, `journalctl -u -b` | +| proxy/DNS | proxied sites time out, node and rules look ok | `dig +short ` must return a fake-ip (`198.18.x.x`); a real IP means the system resolver bypasses `dns-hijack` — mihomo README Gotchas | +| boot | errors at boot, wrong kernel | `journalctl -b -p err`; `just history` for what is booted | +| session | desktop or app misbehaves | `journalctl --user -b -p err`; the `nix-config-desktop` skill | +| remote host | anything on a Colmena host | `ssh root@ journalctl -b -p err`, same commands over SSH | +| MicroVM guest | guest down or unreachable | on the VM host: `systemctl status microvm@` and `microvm-tap-interfaces@`; then `br0` | +| secrets | missing or unreadable `/etc/agenix/*` | the `nix-config-secrets` skill | ## 2. Read the evaluated value diff --git a/.agents/skills/nix-config-new-host/SKILL.md b/.agents/skills/nix-config-new-host/SKILL.md index 6777f434..916db663 100644 --- a/.agents/skills/nix-config-new-host/SKILL.md +++ b/.agents/skills/nix-config-new-host/SKILL.md @@ -59,7 +59,10 @@ assume the physical-host deployment is done through Colmena. unused `args`: haumea passes them lazily and they are still required. 5. `vars/networking.nix` - `hostsAddr. = { iface; ipv4; }` for a LAN host. That entry drives the static address, the SSH `Host` alias used for remote builds, and `known_hosts`, so a wrong - `iface` takes the host offline at activation. Skip it for a DHCP or mobile host. + `iface` takes the host offline at activation. Skip it for a DHCP or mobile host. If the host + enables `modules.networking.mihomo` and runs systemd-resolved, it needs a DNS takeover tied to + mihomo's lifecycle (`resolvectl dns`/`revert` in `ExecStartPost`/`ExecStopPost`, see + `hosts/idols-ai/default.nix`); a static link DNS would kill DNS when mihomo dies. 6. `hosts/README.md` - add the host to the inventory. Pin service user and group ids (`service-user-ids.nix`, as on `shoryu`) before the host has state on diff --git a/hosts/idols-ai/default.nix b/hosts/idols-ai/default.nix index d2e6a068..89f57b42 100644 --- a/hosts/idols-ai/default.nix +++ b/hosts/idols-ai/default.nix @@ -136,6 +136,20 @@ in linkConfig.RequiredForOnline = "routable"; }; + # Dynamic DNS takeover: sing-tun normally asks resolved to use the TUN DNS + # address through resolvectl. That built-in call is not usable from nixpkgs' + # DynamicUser+hardened unit: the D-Bus socket is blocked, and resolve1's + # SetLinkDNS/SetLinkDomains methods require privileged authorization. Without + # it, resolved's upstream queries bypass dns-hijack and leave via the NIC. + # Run the takeover as a privileged lifecycle hook instead. ExecStopPost also + # runs on crashes, so a dead mihomo leaves the network usable. + # The '+' prefix escapes the unit's sandbox; without it, resolvectl's D-Bus + # connect fails and a failed ExecStartPost kills the otherwise healthy service. + systemd.services.mihomo.serviceConfig = { + ExecStartPost = lib.mkDefault "+${pkgs.systemd}/bin/resolvectl dns ${iface} 127.0.0.1:1053"; + ExecStopPost = lib.mkDefault "+${pkgs.systemd}/bin/resolvectl revert ${iface}"; + }; + # This value determines the NixOS release from which the default # settings for stateful data, like file locations and database versions # on your system were taken. It‘s perfectly fine and recommended to leave diff --git a/modules/nixos/desktop/computer-use.nix b/modules/nixos/desktop/computer-use.nix index 30ee1e81..e7e2e6a7 100644 --- a/modules/nixos/desktop/computer-use.nix +++ b/modules/nixos/desktop/computer-use.nix @@ -14,8 +14,6 @@ let timeZone = if cfg.proxyRegion == "jp" then "Asia/Tokyo" else "America/Los_Angeles"; in { - imports = [ ./networking/mihomo ]; # also pulled in by the desktop scanPaths; explicit for clarity - options.modules.desktop.computerUse = { enable = lib.mkEnableOption "headless computer-use environment (X11 + desktop automation)"; proxyRegion = lib.mkOption { @@ -46,10 +44,5 @@ in # Browsers keep their profile/preferences in dconf. programs.dconf.enable = true; - - # Native mihomo runs the proxy as a system service (TUN), so the headless - # session does not depend on a GUI starting the core. Its config lives at - # ~/.config/mihomo/config.yaml (out of the Nix store). - modules.networking.mihomo.enable = true; }; } diff --git a/modules/nixos/desktop/networking/mihomo/README.md b/modules/nixos/desktop/networking/mihomo/README.md index 581073f2..db3bcaeb 100644 --- a/modules/nixos/desktop/networking/mihomo/README.md +++ b/modules/nixos/desktop/networking/mihomo/README.md @@ -1,7 +1,7 @@ # Mihomo Native mihomo core + metacubexd dashboard, replacing the Clash Verge GUI. Enable per host with -`modules.networking.mihomo.enable = true` (`ai`, `shoukei`, `ruby`, `kana`). +`modules.networking.mihomo.enable = true` (`ai`, `shoukei`). ## Usage @@ -46,6 +46,13 @@ targets instead of writing a config the core rejects. won clash-verge-rev#1762). - `find-process-mode: off` unless `PROCESS-*` rules or `tun_exclude_process` need it. - Rule payloads are bare domains: `DOMAIN-SUFFIX,https://qlogo.cn` is invalid. +- On a systemd-resolved host, sing-tun normally points resolved at the TUN DNS with `resolvectl`. + The nixpkgs `DynamicUser` sandbox cannot make those privileged D-Bus changes, so resolved's own + upstream queries bypass `dns-hijack any:53` and leave via the NIC. Tie a resolver takeover to the + service lifetime (`resolvectl dns`/`revert` in `ExecStartPost`/`ExecStopPost`, see + `hosts/idols-ai/default.nix`), prefixed with `+` to run outside the unit sandbox. Never set link + DNS statically to mihomo: a dead mihomo would take DNS down with it, and resolved does not fall + back. ## References diff --git a/modules/nixos/desktop/networking/mihomo/generate.nu b/modules/nixos/desktop/networking/mihomo/generate.nu index 4a201d24..0435684e 100644 --- a/modules/nixos/desktop/networking/mihomo/generate.nu +++ b/modules/nixos/desktop/networking/mihomo/generate.nu @@ -518,7 +518,9 @@ def main [ enable: true # port 53 is unreachable here: the unit runs DynamicUser with only # CAP_NET_ADMIN, so a privileged bind fails. TUN's dns-hijack covers - # resolution; this listener is a loopback convenience only. + # queries that leave through an interface; hosts whose system resolver + # answers over loopback (systemd-resolved) need their resolver taken over + # while mihomo runs, or fake-ip is bypassed entirely. listen: "127.0.0.1:1053" ipv6: false "enhanced-mode": "fake-ip" diff --git a/outputs/x86_64-linux/tests/computer-use-headless/expected.nix b/outputs/x86_64-linux/tests/computer-use-headless/expected.nix index 145a9e34..671bfdac 100644 --- a/outputs/x86_64-linux/tests/computer-use-headless/expected.nix +++ b/outputs/x86_64-linux/tests/computer-use-headless/expected.nix @@ -11,7 +11,7 @@ in lib.genAttrs hosts (name: { computerUse = true; atspi = true; - mihomo = true; + mihomo = false; timeZone = "America/Los_Angeles"; xvfbRestart = "always"; i3Restart = "always";