/ de-omarchy / bin / omarchy-dns
swadhinbiswas Initial de-omarchy: Omarchy UI layer for Arch-based distros · 8/22/2026
9f393b2
302 lines · 7.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
#!/bin/bash

# omarchy:summary=Show or configure the system DNS provider
# omarchy:args=[Cloudflare|Google|DHCP|Custom]
# omarchy:examples=omarchy dns | omarchy dns Cloudflare | omarchy dns Custom

set -euo pipefail

NM_DNS_CONF=/etc/NetworkManager/conf.d/20-omarchy-dns.conf

provider_from_arg() {
  case "${1:-}" in
  Cloudflare | cloudflare)
    echo "Cloudflare"
    ;;
  Google | google)
    echo "Google"
    ;;
  DHCP | dhcp)
    echo "DHCP"
    ;;
  Custom | custom)
    echo "Custom"
    ;;
  *)
    return 1
    ;;
  esac
}

# The path etc/sudoers.d/omarchy-dns names. The privileged half always runs from
# there rather than from whichever copy was invoked, so the rule matches even
# where $OMARCHY_PATH points at a checkout.
PACKAGED_PATH=/usr/bin/omarchy-dns

# True when sudo would run this exact command without stopping for a password.
# `sudo -l` on its own reports whether a command is permitted, which the blanket
# %wheel rule answers yes to for everything; the long listing prints the matched
# entry's tags, so !authenticate is the grant in etc/sudoers.d/omarchy-dns and
# nothing else. Listing runs nothing and, under -n, prompts for nothing, so a
# machine whose omarchy-settings predates that file falls through to polkit
# instead of dying on a password prompt it has no terminal to show.
sudo_grants_passwordless() {
  sudo -n -l -l "$PACKAGED_PATH" "$@" 2>/dev/null | grep -q '!authenticate'
}

require_root() {
  if (( EUID == 0 )); then
    return
  elif [[ -t 0 ]] || sudo_grants_passwordless "$@"; then
    # A terminal can carry sudo's own password prompt. Without one, sudo is
    # right only where the grant reaches; polkit can at least put a prompt on
    # screen, and offer to authenticate as someone else.
    exec sudo "$PACKAGED_PATH" "$@"
  else
    exec pkexec "$PACKAGED_PATH" "$@"
  fi
}

networkmanager_global_dns() {
  [[ -f $NM_DNS_CONF ]] || return 0

  awk -F= '
    /^[[:space:]]*#/ { next }
    /^[[:space:]]*\[global-dns-domain-\*\][[:space:]]*$/ { in_default = 1; next }
    /^[[:space:]]*\[/ { in_default = 0 }
    in_default && /^[[:space:]]*servers[[:space:]]*=/ {
      value = $0
      sub(/^[^=]*=/, "", value)
      print value
      exit
    }
  ' "$NM_DNS_CONF"
}

resolved_dns() {
  awk -F= '
    /^[[:space:]]*#/ { next }
    /^[[:space:]]*DNS[[:space:]]*=/ {
      value=$0
      sub(/^[^=]*=/, "", value)
      print value
      exit
    }
  ' /etc/systemd/resolved.conf 2>/dev/null || true
}

current_dns_provider() {
  local dns=""
  local compact=""

  dns=$(networkmanager_global_dns)
  if [[ -z $(printf '%s' "$dns" | tr -d '[:space:],') ]]; then
    dns=$(resolved_dns)
  fi

  compact=$(printf '%s' "$dns" | tr -d '[:space:],')

  if [[ -z $compact ]]; then
    echo "DHCP"
  elif [[ $dns == *"cloudflare-dns.com"* || $dns == *"1.1.1.1"* || $dns == *"2606:4700:4700::1111"* ]]; then
    echo "Cloudflare"
  elif [[ $dns == *"dns.google"* || $dns == *"8.8.8.8"* || $dns == *"2001:4860:4860::8888"* ]]; then
    echo "Google"
  else
    echo "Custom"
  fi
}

normalize_servers() {
  printf '%s\n' "$*" | tr ',\t\n' ' ' | xargs | tr ' ' ','
}

split_dns_servers() {
  local servers="$1"
  local server clean
  ipv4_dns=""
  ipv6_dns=""

  for server in ${servers//,/ }; do
    clean=${server#dns+tls://}
    clean=${clean#dns+udp://}
    clean=${clean%%#*}
    clean=${clean#[}
    clean=${clean%]}

    [[ -n $clean ]] || continue
    if [[ $clean == *:* ]]; then
      ipv6_dns+="${ipv6_dns:+ }$clean"
    else
      ipv4_dns+="${ipv4_dns:+ }$clean"
    fi
  done
}

write_networkmanager_dns() {
  local servers="$1"

  install -d -m 0755 "$(dirname "$NM_DNS_CONF")"
  cat >"$NM_DNS_CONF" <<EOF
# Managed by omarchy-dns. Remove this file or run omarchy dns DHCP to use DHCP DNS again.
[global-dns]

[global-dns-domain-*]
servers=$servers
EOF
}

clear_networkmanager_dns() {
  rm -f "$NM_DNS_CONF"
}

networkmanager_dns_connection() {
  case "$1" in
    802-11-wireless|802-3-ethernet) return 0 ;;
    *) return 1 ;;
  esac
}

set_connection_dns() {
  local uuid type
  local ipv4_dns="${1:-}"
  local ipv6_dns="${2:-}"

  while IFS=: read -r uuid type; do
    [[ -n $uuid ]] || continue
    networkmanager_dns_connection "$type" || continue

    nmcli connection modify "$uuid" \
      ipv4.ignore-auto-dns yes \
      ipv4.dns "$ipv4_dns" \
      ipv6.ignore-auto-dns yes \
      ipv6.dns "$ipv6_dns" \
      >/dev/null
  done < <(nmcli -t -f UUID,TYPE connection show)
}

clear_connection_dns() {
  local uuid type

  while IFS=: read -r uuid type; do
    [[ -n $uuid ]] || continue
    networkmanager_dns_connection "$type" || continue

    nmcli connection modify "$uuid" \
      ipv4.ignore-auto-dns no \
      ipv4.dns "" \
      ipv6.ignore-auto-dns no \
      ipv6.dns "" \
      >/dev/null
  done < <(nmcli -t -f UUID,TYPE connection show)
}

reapply_active_dns_connections() {
  local device type state

  while IFS=: read -r device type state; do
    [[ -n $device && $state == connected ]] || continue
    case "$type" in
      wifi|ethernet)
        nmcli device reapply "$device" >/dev/null 2>&1 || true
        ;;
    esac
  done < <(nmcli -t -f DEVICE,TYPE,STATE device status)
}

reload_dns_stack() {
  if systemctl is-active --quiet NetworkManager.service 2>/dev/null; then
    # Load the updated NetworkManager config first, then reapply the active
    # profiles. A single conf,dns-full reload here pushes the old active DNS
    # settings, making the shell toggle appear one selection behind.
    nmcli general reload conf >/dev/null 2>&1 || systemctl reload NetworkManager.service 2>/dev/null || true
    reapply_active_dns_connections
  fi

  systemctl reload systemd-resolved.service 2>/dev/null || systemctl restart systemd-resolved.service

  if systemctl is-active --quiet NetworkManager.service 2>/dev/null; then
    # A resolved reload/restart can leave per-link DNS stale or empty; ask
    # NetworkManager to publish DNS after resolved has reread its config.
    nmcli general reload dns-full >/dev/null 2>&1 || true
  fi
}

usage() {
  echo "Usage: omarchy-dns [Cloudflare|Google|DHCP|Custom]" >&2
}

if (( $# == 0 )); then
  current_dns_provider
  exit 0
fi

if (( $# > 1 )); then
  usage
  exit 1
fi

if ! provider=$(provider_from_arg "$1"); then
  usage
  exit 1
fi

require_root "$provider"

case "$provider" in
Cloudflare)
  write_networkmanager_dns "1.1.1.1,1.0.0.1,2606:4700:4700::1111,2606:4700:4700::1001"
  set_connection_dns "1.1.1.1 1.0.0.1" "2606:4700:4700::1111 2606:4700:4700::1001"
  tee /etc/systemd/resolved.conf >/dev/null <<'EOF'
[Resolve]
DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 2606:4700:4700::1111#cloudflare-dns.com 2606:4700:4700::1001#cloudflare-dns.com
FallbackDNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net
DNSOverTLS=opportunistic
EOF
  ;;

Google)
  write_networkmanager_dns "8.8.8.8,8.8.4.4,2001:4860:4860::8888,2001:4860:4860::8844"
  set_connection_dns "8.8.8.8 8.8.4.4" "2001:4860:4860::8888 2001:4860:4860::8844"
  tee /etc/systemd/resolved.conf >/dev/null <<'EOF'
[Resolve]
DNS=8.8.8.8#dns.google 8.8.4.4#dns.google 2001:4860:4860::8888#dns.google 2001:4860:4860::8844#dns.google
FallbackDNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net
DNSOverTLS=opportunistic
EOF
  ;;

DHCP)
  clear_networkmanager_dns
  clear_connection_dns
  tee /etc/systemd/resolved.conf >/dev/null <<'EOF'
[Resolve]
DNSOverTLS=no
EOF
  ;;

Custom)
  echo "Enter your DNS servers (space-separated, e.g. '192.168.1.1 1.1.1.1'):"
  if ! read -r dns_servers; then
    dns_servers=""
  fi

  dns_servers=$(normalize_servers "$dns_servers")
  if [[ -z $dns_servers ]]; then
    echo "Error: No DNS servers provided." >&2
    exit 1
  fi

  split_dns_servers "$dns_servers"
  write_networkmanager_dns "$dns_servers"
  set_connection_dns "$ipv4_dns" "$ipv6_dns"
  tee /etc/systemd/resolved.conf >/dev/null <<EOF
[Resolve]
DNS=${dns_servers//,/ }
FallbackDNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net 2620:fe::fe#dns.quad9.net 2620:fe::9#dns.quad9.net
EOF
  ;;
esac

reload_dns_stack