Skip to content

Commit 9ddc34f

Browse files
authored
Merge pull request #90 from geodro/fix/omarchy-dns-support
fix: support omarchy and other systemd-resolved-only systems
2 parents 6d63709 + 2999500 commit 9ddc34f

File tree

8 files changed

+77
-14
lines changed

8 files changed

+77
-14
lines changed

docs/getting-started/requirements.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Requirements
22

3-
- **Linux** — Arch, Debian/Ubuntu, or Fedora-based
3+
- **Linux** — Arch, Debian/Ubuntu, Fedora-based, or omarchy
44
- **[Podman](https://podman.io/)** — rootless, with systemd user session active
5-
- **[NetworkManager](https://networkmanager.dev/)**for `.test` DNS
5+
- **DNS resolver**[NetworkManager](https://networkmanager.dev/) or [systemd-resolved](https://www.freedesktop.org/software/systemd/man/systemd-resolved.service.html) (at least one is required for `.test` DNS)
66
- **`systemctl --user` functional** — run `loginctl enable-linger $USER` if needed
77

88
::: warning Linger must be enabled

docs/reference/architecture.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ All containers join the rootless Podman network `lerd`. Communication between Ng
88
*.test DNS
99
1010
┌──────────┴──────────┐
11-
│ NetworkManager │
11+
│ DNS resolver │
12+
│ (NM or resolved) │
1213
└──────────┬──────────┘
1314
│ forwards .test queries
1415
┌──────────┴──────────┐
@@ -43,12 +44,12 @@ All containers join the rootless Podman network `lerd`. Communication between Ng
4344
| Composer | `composer.phar` via bundled PHP CLI |
4445
| Node | [fnm](https://github.com/Schniz/fnm) binary, version per project |
4546
| Services | Podman Quadlet containers |
46-
| DNS | dnsmasq container + NetworkManager integration |
47+
| DNS | dnsmasq container + NetworkManager or systemd-resolved integration |
4748
| TLS | [mkcert](https://github.com/FiloSottile/mkcert) — locally trusted CA |
4849

4950
## Key design decisions
5051

51-
**Rootless Podman** — all containers run without root privileges. The only operations requiring `sudo` are DNS setup (writes to `/etc/NetworkManager/`) and the initial `net.ipv4.ip_unprivileged_port_start=80` sysctl.
52+
**Rootless Podman** — all containers run without root privileges. The only operations requiring `sudo` are DNS setup (configures NetworkManager or systemd-resolved to route `.test` queries) and the initial `net.ipv4.ip_unprivileged_port_start=80` sysctl.
5253

5354
**Podman Quadlets** — containers are defined as systemd unit files (`.container` files) managed by the Quadlet generator. This means `systemctl --user start lerd-nginx` works like any other systemd service, and containers restart on failure and at login.
5455

docs/troubleshooting.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,19 @@ Run the DNS check first:
1818
lerd dns:check
1919
```
2020

21-
If it fails, restart NetworkManager and check again:
21+
If it fails, restart your DNS resolver and check again:
2222

2323
```bash
24+
# NetworkManager systems:
2425
sudo systemctl restart NetworkManager
26+
27+
# systemd-resolved only (e.g. omarchy):
28+
sudo systemctl restart systemd-resolved
29+
2530
lerd dns:check
2631
```
2732

28-
On systems using systemd-resolved (Ubuntu), check that the per-interface DNS configuration was applied:
33+
On systems using systemd-resolved, check that the DNS configuration was applied:
2934

3035
```bash
3136
resolvectl status

install.sh

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,13 @@ check_systemd_user() {
8686
fi
8787
}
8888

89-
check_nm() {
89+
check_dns_resolver() {
9090
if systemctl is-active --quiet NetworkManager 2>/dev/null; then
9191
success "NetworkManager running"
92+
elif systemctl is-active --quiet systemd-resolved 2>/dev/null; then
93+
success "systemd-resolved running"
9294
else
93-
warn "NetworkManager not running — required for .test DNS"
95+
warn "No supported DNS resolver running (need NetworkManager or systemd-resolved)"
9496
MISSING_PKGS+=("networkmanager")
9597
fi
9698
}
@@ -129,7 +131,7 @@ check_prerequisites() {
129131

130132
check_cmd podman podman "container runtime"
131133
check_cmd unzip unzip "needed to extract fnm"
132-
check_nm
134+
check_dns_resolver
133135
check_systemd_user
134136
check_podman_rootless
135137
check_certutil

internal/cli/doctor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ func runDoctor(_ *cobra.Command, _ []string) error {
152152
ok(fmt.Sprintf(".%s resolves to 127.0.0.1", tld))
153153
} else {
154154
fail(fmt.Sprintf(".%s resolution", tld), "not resolving to 127.0.0.1",
155-
"run 'lerd install' or: sudo systemctl restart NetworkManager")
155+
"run 'lerd install' or: "+dns.ResolverHint())
156156
}
157157
}
158158

internal/cli/status.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func runStatus(_ *cobra.Command, _ []string) error {
5959
} else {
6060
fail2(fmt.Sprintf(".%s resolution", cfg.DNS.TLD),
6161
"not resolving",
62-
"run 'lerd install' to reconfigure, or: sudo systemctl restart NetworkManager")
62+
"run 'lerd install' to reconfigure, or: "+dns.ResolverHint())
6363
}
6464

6565
// Nginx

internal/dns/setup.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func isFileContent(path string, content []byte) bool {
9090
}
9191

9292
// isSystemdResolvedActive returns true if systemd-resolved is the active DNS resolver.
93-
func isSystemdResolvedActive() bool {
93+
var isSystemdResolvedActive = func() bool {
9494
cmd := exec.Command("systemctl", "is-active", "--quiet", "systemd-resolved")
9595
if err := cmd.Run(); err != nil {
9696
return false
@@ -104,11 +104,22 @@ func isSystemdResolvedActive() bool {
104104
}
105105

106106
// isNetworkManagerActive returns true if NetworkManager is running.
107-
func isNetworkManagerActive() bool {
107+
var isNetworkManagerActive = func() bool {
108108
cmd := exec.Command("systemctl", "is-active", "--quiet", "NetworkManager")
109109
return cmd.Run() == nil
110110
}
111111

112+
// ResolverHint returns a user-facing hint for restarting the active DNS resolver.
113+
func ResolverHint() string {
114+
if isNetworkManagerActive() {
115+
return "sudo systemctl restart NetworkManager"
116+
}
117+
if isSystemdResolvedActive() {
118+
return "sudo systemctl restart systemd-resolved"
119+
}
120+
return "restart your DNS resolver"
121+
}
122+
112123
// defaultInterface returns the name of the default network interface (e.g. "enp1s0").
113124
func defaultInterface() string {
114125
out, err := exec.Command("ip", "route", "show", "default").Output()

internal/dns/setup_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,50 @@ func TestWriteDnsmasqConfig_noUpstreams(t *testing.T) {
184184
}
185185
}
186186

187+
// --- ResolverHint ---
188+
189+
func TestResolverHint_NetworkManager(t *testing.T) {
190+
origNM := isNetworkManagerActive
191+
origResolved := isSystemdResolvedActive
192+
defer func() { isNetworkManagerActive = origNM; isSystemdResolvedActive = origResolved }()
193+
194+
isNetworkManagerActive = func() bool { return true }
195+
isSystemdResolvedActive = func() bool { return true }
196+
197+
got := ResolverHint()
198+
if got != "sudo systemctl restart NetworkManager" {
199+
t.Errorf("expected NM hint, got %q", got)
200+
}
201+
}
202+
203+
func TestResolverHint_SystemdResolvedOnly(t *testing.T) {
204+
origNM := isNetworkManagerActive
205+
origResolved := isSystemdResolvedActive
206+
defer func() { isNetworkManagerActive = origNM; isSystemdResolvedActive = origResolved }()
207+
208+
isNetworkManagerActive = func() bool { return false }
209+
isSystemdResolvedActive = func() bool { return true }
210+
211+
got := ResolverHint()
212+
if got != "sudo systemctl restart systemd-resolved" {
213+
t.Errorf("expected systemd-resolved hint, got %q", got)
214+
}
215+
}
216+
217+
func TestResolverHint_NoResolver(t *testing.T) {
218+
origNM := isNetworkManagerActive
219+
origResolved := isSystemdResolvedActive
220+
defer func() { isNetworkManagerActive = origNM; isSystemdResolvedActive = origResolved }()
221+
222+
isNetworkManagerActive = func() bool { return false }
223+
isSystemdResolvedActive = func() bool { return false }
224+
225+
got := ResolverHint()
226+
if got != "restart your DNS resolver" {
227+
t.Errorf("expected generic hint, got %q", got)
228+
}
229+
}
230+
187231
// --- helpers ---
188232

189233
func writeTempFile(t *testing.T, content string) string {

0 commit comments

Comments
 (0)