cmux-vm image: keep the resolver on systemd-resolved and check DNS survives a resolved/tailscaled restart
Leo, about huge-blue (2026-10-25): "Like we need to have good ass images, good ass ergonomics, good ass everything."
What broke
On huge-blue, Tailscale ran in "direct" mode and wrote /etc/resolv.conf itself with "no upstream resolvers set", so nothing resolved. Someone fixed it by hand: /etc/resolv.conf now links to systemd-resolved's stub, and Tailscale is in resolved mode. Tailscale picks resolved mode only when resolv.conf is the resolved stub and resolved is running, so the link is what makes it safe. Persistent cloud machines need that guarantee too.
What this changes
-
images/cmux-vm/machine-health/cmux-dns-check: a standalone bash check that changes nothing except two restarts. It is baked as/usr/local/sbin/cmux-dns-checkand runs as-is on huge-blue. It checks:/etc/resolv.confis a link to/run/systemd/resolve/stub-resolv.conf- systemd-resolved is active
- Tailscale's DNS mode in the current tailscaled run's journal (
dns: [... ret=MODE]);directfails - a public name resolves (default github.com)
- the machine's own MagicDNS name resolves when
tailscale status --jsonshows BackendState Running and MagicDNS on; otherwise the check is skipped with the reason printed
--restartfirst restarts systemd-resolved, then tailscaled when it is installed. -
Bake (
dns.tsresolverBakeCommand): keeps systemd-resolved enabled and active, and records whether resolv.conf already linked to the stub. If it did not, the bake makes the link, but only when the stub lists a nameserver. It also records the provider'sresolved.conf.dentries without editing them (60-freestyle-vms.confis untouched), then runs the check. The result is inbake-*.jsonasresolver. -
Smoke: a new
dns-resolved-restartcheck on a clone runscmux-dns-check --restartafter the automation checks. -
Tailscale is not in the image (no package in the lock, the base dpkg list or the file manifest), and nothing in the image forces direct mode. On machines that do install Tailscale, the check reads the mode tailscaled actually picked.
Evidence
web/tests/vm-image-cmux-vm-dns.test.tsruns the script against stubbed systemctl, journalctl, getent and tailscale. Cases: a healthy machine,--restartorder (resolved, then tailscaled), the huge-blue breakage (plain resolv.conf,ret=direct, nothing resolving: exit 1 with each FAIL), logged-out and MagicDNS-off tailnets (skip with the reason), and bad usage. It was added tocloud-vm-image-lock.yml.- Fleet Linux run (cmux-ci, run 40234697671):
bun testof the dns, commands, lock and bake-workflow suites gave 57 pass, 0 fail.tsgo --noEmitand eslint on the changed files were both clean. - Bake and smoke on a real VM:
cloud-vm-image-bake.ymlruns only on feat-cmux-next, so it is dispatched after this lands. The result goes in a comment here.
Risk: low. One new image file, one bake step that only checks (and links only when the base has no link and resolved is serving), and one smoke check. No timer, loop or resident process.
Run on huge-blue: sudo bash images/cmux-vm/machine-health/cmux-dns-check --restart from a checkout at this commit.
Agent session: Air-Blue
š¤ Generated with Claude Code
![View with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png)
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Need help on this PR? Tag @codesmith-bot with what you need.