Skip to content

fix(system): handle the DNS resolver step without root on system:install#205

Open
mawiswiss wants to merge 1 commit into
v2from
fix/macos-dns-resolver-elevation
Open

fix(system): handle the DNS resolver step without root on system:install#205
mawiswiss wants to merge 1 commit into
v2from
fix/macos-dns-resolver-elevation

Conversation

@mawiswiss

@mawiswiss mawiswiss commented Jun 11, 2026

Copy link
Copy Markdown
Member

Problem

On macOS, dde system:install fails the Configuring DNS resolver step:

Configuring DNS resolver... failed — Cannot rename "/var/folders/.../Txxxx" to "/etc/resolver/test": Permission denied

This shows up mainly when upgrading from dde v1: v1 left a /etc/resolver/test containing nameserver 127.0.0.1 without a trailing newline, while v2 expects the newline. The exact-match idempotency check therefore never short-circuits, so dde tries to rewrite the root-owned file as a normal user and dies with a raw Symfony rename exception.

Fix (minimal)

Writing /etc/resolver/test needs root, and the people running dde system:install are technical — so rather than build elevation into the installer, this just makes the failure mode sane:

  • Tolerant idempotency — the existing-file check now compares trimmed content, so the v1 leftover file is recognised as already configured. Upgrading needs no root and no manual step at all.

  • Actionable error — when the file genuinely has to be created and dde can't (not root), it now prints the exact command instead of a cryptic rename exception:

    Could not write /etc/resolver/test, which requires root. Create it manually and re-run 'dde system:install':
      echo 'nameserver 127.0.0.1' | sudo tee /etc/resolver/test
    

That's the whole change: +21/−5 across DnsmasqService and the CHANGELOG, no new classes.

Testing

make qa green — ECS, PHPStan L8, Rector, PHPUnit (1278 tests).

Notes

  • Linux paths are unchanged.
  • No Homebrew caveat change needed.

`dde system:install` writes /etc/resolver/test, which requires root. Run as a
normal user the step failed with a cryptic Symfony rename exception ("Cannot
rename ... Permission denied").

Catch that and surface the exact command to run instead:

  echo 'nameserver 127.0.0.1' | sudo tee /etc/resolver/test

The existing-file check now compares trimmed content, so a resolver file left
behind by dde v1 (which lacks a trailing newline) is recognised as already
configured — re-running system:install after a v1 upgrade no longer touches the
file or needs root at all.

Signed-off-by: Marc Wieland <marc@whatwedo.ch>
@mawiswiss mawiswiss force-pushed the fix/macos-dns-resolver-elevation branch from 5f3e590 to 00ad9eb Compare June 11, 2026 11:26
@mawiswiss mawiswiss changed the title fix(system): self-elevate the DNS resolver step on system:install fix(system): handle the DNS resolver step without root on system:install Jun 11, 2026
@mawiswiss mawiswiss requested a review from sbaerlocher June 11, 2026 11:36
@mawiswiss

Copy link
Copy Markdown
Member Author

@sbaerlocher Not sure if your PR (#142) already solves this issue.

sbaerlocher added a commit to sbaerlocher/dde that referenced this pull request Jun 30, 2026
`dde system:install` now configures DNS on Linux (systemd-resolved and
NetworkManager) without a `sudo` prefix, and rejects `sudo dde` up-front so
`$HOME/.dde/**` can no longer be poisoned with root-owned files.

Two production failures from sbaerlocher/savvy PR whatwedo#102 motivated the change:
Run 25183781831 (no sudo) failed at the DNS step with `mkdir(): Permission
denied` on `/etc/systemd/resolved.conf.d/`; Run 25138709404 (with sudo) ran
as root, then the next `dde project:up` as the unprivileged runner failed
because `$HOME/.dde/data/dnsmasq/dnsmasq.conf` was owned by uid=0.

PrivilegeEscalator implements an optimistic-then-sudo pattern: each write is
attempted as the current user and only retried through sudo on a permission
error, with the sudo TTY forwarded for interactive prompts. The bin/console
guard runs before kernel boot and rejects the unique EUID-0-plus-SUDO_USER
signature of `sudo dde`, while letting real-root sessions (containers,
provisioning) pass through. DnsmasqService routes the two Linux /etc call
sites through the escalator; ensureConfig() under $DDE_DATA_DIR deliberately
stays on the bare Filesystem so user-data ownership is preserved. The macOS
/etc/resolver/test path is left to whatwedo#205 and stays on the bare Filesystem.

Signed-off-by: Simon Bärlocher <simon@whatwedo.ch>
Signed-off-by: Simon Bärlocher <s.baerlocher@sbaerlocher.ch>
sbaerlocher added a commit to sbaerlocher/dde that referenced this pull request Jun 30, 2026
End-to-end coverage for the no-sudo contract: the Linux happy path that
configures DNS without sudo, the `sudo dde` rejection, $DDE_DATA_DIR staying
owned by the invoking user, and a follow-up `project:up` still being able to
write under $DDE_DATA_DIR. Each case owns its setUp/tearDown with an isolated
config/data tree and best-effort cleanup of any /etc artefacts. The macOS
resolver path is out of scope for this suite (handled by whatwedo#205).

Signed-off-by: Simon Bärlocher <simon@whatwedo.ch>
Signed-off-by: Simon Bärlocher <s.baerlocher@sbaerlocher.ch>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant