Fix NetworkManager transition during upgrade

This commit is contained in:
Ryan Hughes
2026-06-21 02:25:42 -04:00
parent 35a6940992
commit 34ddf5d54e
6 changed files with 402 additions and 54 deletions
+217 -49
View File
@@ -6,11 +6,69 @@
set -euo pipefail
current_dns_provider() {
local dns=""
local compact=""
NM_DNS_CONF=/etc/NetworkManager/conf.d/20-omarchy-dns.conf
dns=$(awk -F= '
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
}
self_path() {
local self="${BASH_SOURCE[0]}"
if [[ $self != /* ]]; then
self=$(command -v -- "$self")
fi
readlink -f "$self"
}
require_root() {
if (( EUID == 0 )); then
return
fi
if ! command -v pkexec >/dev/null; then
echo "Error: pkexec is required to change DNS settings." >&2
exit 1
fi
exec pkexec "$(self_path)" "$@"
}
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
@@ -18,9 +76,19 @@ current_dns_provider() {
print value
exit
}
' /etc/systemd/resolved.conf 2>/dev/null || true)
' /etc/systemd/resolved.conf 2>/dev/null || true
}
compact=$(printf '%s' "$dns" | tr -d '[:space:]')
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"
@@ -33,37 +101,129 @@ current_dns_provider() {
fi
}
require_root() {
if (( EUID != 0 )); then
exec pkexec omarchy-dns "$@"
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:-}"
command -v nmcli >/dev/null 2>&1 || return 0
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
command -v nmcli >/dev/null 2>&1 || return 0
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
command -v nmcli >/dev/null 2>&1 || return 0
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
}
lock_dns_to_resolved() {
local file=""
for file in /etc/systemd/network/*.network; do
[[ -f $file ]] || continue
if ! grep -q "^\[DHCPv4\]" "$file"; then continue; fi
if ! sed -n '/^\[DHCPv4\]/,/^\[/p' "$file" | grep -q "^UseDNS="; then
sed -i '/^\[DHCPv4\]/a UseDNS=no' "$file"
fi
if grep -q "^\[IPv6AcceptRA\]" "$file" && ! sed -n '/^\[IPv6AcceptRA\]/,/^\[/p' "$file" | grep -q "^UseDNS="; then
sed -i '/^\[IPv6AcceptRA\]/a UseDNS=no' "$file"
fi
done
}
unlock_dns_to_dhcp() {
local file=""
for file in /etc/systemd/network/*.network; do
[[ -f $file ]] || continue
sed -i '/^\[DHCPv4\]/{n;/^UseDNS=no$/d}' "$file"
sed -i '/^\[IPv6AcceptRA\]/{n;/^UseDNS=no$/d}' "$file"
done
usage() {
echo "Usage: omarchy-dns [Cloudflare|Google|DHCP|Custom]" >&2
}
if (( $# == 0 )); then
@@ -71,63 +231,71 @@ if (( $# == 0 )); then
exit 0
fi
provider="$1"
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)
require_root "$provider"
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
lock_dns_to_resolved
;;
Google)
require_root "$provider"
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
lock_dns_to_resolved
;;
DHCP)
require_root "$provider"
clear_networkmanager_dns
clear_connection_dns
tee /etc/systemd/resolved.conf >/dev/null <<'EOF'
[Resolve]
DNSOverTLS=no
EOF
unlock_dns_to_dhcp
;;
Custom)
require_root "$provider"
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
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
lock_dns_to_resolved
;;
*)
echo "Usage: omarchy-dns [Cloudflare|Google|DHCP|Custom]" >&2
exit 1
;;
esac
systemctl restart systemd-networkd systemd-resolved
reload_dns_stack
+16 -2
View File
@@ -848,10 +848,23 @@ USER_UNITS
cleanup_retired_services() {
log "Disabling services retired by Omarchy 4"
# Do not stop iwd mid-upgrade; disabling is enough and avoids dropping a
# running Wi-Fi connection before the user can reboot into NetworkManager.
# Do not stop iwd or systemd-networkd mid-upgrade; disabling is enough and
# avoids dropping a running Wi-Fi connection before the user can reboot into
# NetworkManager.
as_root systemctl disable iwd.service >/dev/null 2>&1 || true
local networkd_unit
for networkd_unit in \
systemd-networkd.service \
systemd-networkd.socket \
systemd-networkd-varlink.socket \
systemd-networkd-varlink-metrics.socket \
systemd-networkd-resolve-hook.socket; do
as_root systemctl disable "$networkd_unit" >/dev/null 2>&1 || true
done
as_root systemctl disable systemd-networkd-wait-online.service >/dev/null 2>&1 || true
as_root systemctl mask systemd-networkd-wait-online.service >/dev/null 2>&1 || true
cleanup_retired_user_unit_files
if [[ -S $target_runtime_dir/bus ]]; then
run_as_user_session systemctl --user disable \
@@ -1050,6 +1063,7 @@ run_post_upgrade_migrations() {
USER="$target_user" \
LOGNAME="$target_user" \
OMARCHY_PATH=/usr/share/omarchy \
OMARCHY_UPGRADE_TO_4_LIVE=1 \
PATH="$package_path" \
omarchy-migrate; then
warn "Could not run Omarchy migrations; the user may be prompted to run them after login."
+40 -3
View File
@@ -1,6 +1,43 @@
# NetworkManager enablement is centralized in enable-services.sh.
systemctl disable iwd.service 2>/dev/null || true
# Prevent systemd-networkd-wait-online timeout on boot
systemctl disable systemd-networkd-wait-online.service
systemctl mask systemd-networkd-wait-online.service
# Fresh Omarchy uses NetworkManager. Archinstall's legacy "copy ISO network"
# mode enabled systemd-networkd and dropped DHCP .network files that compete
# with NetworkManager, so retire that state whenever hardware setup runs.
for unit in \
systemd-networkd.service \
systemd-networkd.socket \
systemd-networkd-varlink.socket \
systemd-networkd-varlink-metrics.socket \
systemd-networkd-resolve-hook.socket; do
systemctl disable "$unit" 2>/dev/null || true
done
# Prevent systemd-networkd-wait-online timeout on boot.
systemctl disable systemd-networkd-wait-online.service 2>/dev/null || true
systemctl mask systemd-networkd-wait-online.service 2>/dev/null || true
stock_networkd_file() {
local file="$1"
[[ -f $file ]] || return 1
case "$(basename "$file")" in
20-ethernet.network|20-wlan.network|20-wwan.network) ;;
*) return 1 ;;
esac
grep -Eq '^[[:space:]]*DHCP=yes[[:space:]]*$' "$file" || return 1
grep -Eq '^[[:space:]]*Name=(en\*|eth\*|wl\*|ww\*)[[:space:]]*$' "$file" || return 1
}
backup_dir="/etc/systemd/network/omarchy-networkd-retired-$(date +%Y%m%d%H%M%S)"
for file in /etc/systemd/network/20-ethernet.network /etc/systemd/network/20-wlan.network /etc/systemd/network/20-wwan.network; do
if stock_networkd_file "$file"; then
install -d -m 0755 "$backup_dir"
mv "$file" "$backup_dir/"
fi
done
if systemctl is-active --quiet NetworkManager.service 2>/dev/null; then
systemctl stop systemd-networkd.service 2>/dev/null || true
fi
+88
View File
@@ -0,0 +1,88 @@
echo "Retire systemd-networkd in favor of NetworkManager"
as_root() {
if (( EUID == 0 )); then
"$@"
else
sudo "$@"
fi
}
stock_networkd_file() {
local file="$1"
[[ -f $file ]] || return 1
case "$(basename "$file")" in
20-ethernet.network|20-wlan.network|20-wwan.network) ;;
*) return 1 ;;
esac
grep -Eq '^[[:space:]]*DHCP=yes[[:space:]]*$' "$file" || return 1
grep -Eq '^[[:space:]]*Name=(en\*|eth\*|wl\*|ww\*)[[:space:]]*$' "$file" || return 1
}
backup_stock_networkd_files() {
local backup_dir="/etc/systemd/network/omarchy-networkd-retired-$(date +%Y%m%d%H%M%S)"
local file
for file in /etc/systemd/network/20-ethernet.network /etc/systemd/network/20-wlan.network /etc/systemd/network/20-wwan.network; do
if stock_networkd_file "$file"; then
as_root install -d -m 0755 "$backup_dir"
as_root mv "$file" "$backup_dir/"
fi
done
}
networkd_units=(
systemd-networkd.service
systemd-networkd.socket
systemd-networkd-varlink.socket
systemd-networkd-varlink-metrics.socket
systemd-networkd-resolve-hook.socket
)
if [[ ${OMARCHY_UPGRADE_TO_4_LIVE:-0} == "1" ]]; then
as_root systemctl enable NetworkManager.service >/dev/null 2>&1 || true
if systemctl is-active --quiet NetworkManager.service 2>/dev/null; then
# NetworkManager is already carrying the live network; it is safe to stop
# networkd now so the rest of the upgrade no longer has competing DHCP.
for unit in "${networkd_units[@]}"; do
as_root systemctl disable --now "$unit" >/dev/null 2>&1 || true
done
as_root systemctl disable systemd-networkd-wait-online.service >/dev/null 2>&1 || true
as_root systemctl mask systemd-networkd-wait-online.service >/dev/null 2>&1 || true
backup_stock_networkd_files
as_root systemctl stop systemd-networkd.service >/dev/null 2>&1 || true
as_root systemctl reload NetworkManager.service >/dev/null 2>&1 || true
as_root systemctl restart systemd-resolved.service >/dev/null 2>&1 || true
else
# Older live upgrades may still be relying on networkd/iwd. Disable for the
# next boot, but do not stop or reconfigure the running link.
for unit in "${networkd_units[@]}"; do
as_root systemctl disable "$unit" >/dev/null 2>&1 || true
done
as_root systemctl disable systemd-networkd-wait-online.service >/dev/null 2>&1 || true
as_root systemctl mask systemd-networkd-wait-online.service >/dev/null 2>&1 || true
fi
exit 0
fi
if ! command -v NetworkManager >/dev/null 2>&1; then
omarchy-pkg-add networkmanager
fi
as_root systemctl enable --now NetworkManager.service >/dev/null 2>&1 || true
for unit in "${networkd_units[@]}"; do
as_root systemctl disable --now "$unit" >/dev/null 2>&1 || true
done
as_root systemctl disable systemd-networkd-wait-online.service >/dev/null 2>&1 || true
as_root systemctl mask systemd-networkd-wait-online.service >/dev/null 2>&1 || true
backup_stock_networkd_files
as_root systemctl stop systemd-networkd.service >/dev/null 2>&1 || true
as_root systemctl reload NetworkManager.service >/dev/null 2>&1 || true
as_root systemctl restart systemd-resolved.service >/dev/null 2>&1 || true
@@ -0,0 +1,35 @@
#!/bin/bash
set -euo pipefail
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
dns="$ROOT/bin/omarchy-dns"
hardware_network="$ROOT/install/hardware/network.sh"
migration="$ROOT/migrations/1782002156.sh"
! grep -F 'systemd-networkd' "$dns" >/dev/null || fail "omarchy-dns no longer restarts systemd-networkd"
grep -F 'NetworkManager/conf.d/20-omarchy-dns.conf' "$dns" >/dev/null
grep -F '[global-dns-domain-*]' "$dns" >/dev/null
grep -F 'ipv4.ignore-auto-dns yes' "$dns" >/dev/null
grep -F 'ipv4.ignore-auto-dns no' "$dns" >/dev/null
grep -F 'nmcli device reapply' "$dns" >/dev/null
grep -F 'nmcli general reload conf' "$dns" >/dev/null
grep -F 'nmcli general reload dns-full' "$dns" >/dev/null
if grep -F 'nmcli general reload conf,dns-full' "$dns" >/dev/null; then
fail "omarchy-dns must not push DNS before reapplying active profiles"
fi
pass "omarchy-dns configures DNS through NetworkManager"
grep -F 'systemd-networkd.service' "$hardware_network" >/dev/null
grep -F 'systemd-networkd.socket' "$hardware_network" >/dev/null
grep -F '20-wlan.network' "$hardware_network" >/dev/null
grep -F 'omarchy-networkd-retired' "$hardware_network" >/dev/null
pass "hardware setup retires archinstall networkd state"
grep -F 'OMARCHY_UPGRADE_TO_4_LIVE' "$migration" >/dev/null
grep -F 'systemctl disable --now "$unit"' "$migration" >/dev/null
grep -F 'systemctl stop systemd-networkd.service' "$migration" >/dev/null
grep -F 'NetworkManager.service' "$migration" >/dev/null
grep -F '20-wlan.network' "$migration" >/dev/null
pass "migration repairs upgraded systems with networkd still active"
+6
View File
@@ -24,3 +24,9 @@ grep -F 'skip-first-run-update-notification' "$upgrade_to_4" >/dev/null
grep -F 'skip-first-run-update-notification' "$first_run_wifi" >/dev/null
grep -F '(( skip_update_notification )) && return 0' "$first_run_wifi" >/dev/null
pass "Omarchy 4 upgrade suppresses the fresh-install update toast"
grep -F 'OMARCHY_UPGRADE_TO_4_LIVE=1' "$upgrade_to_4" >/dev/null
grep -F 'systemd-networkd.service' "$upgrade_to_4" >/dev/null
grep -F 'systemd-networkd.socket' "$upgrade_to_4" >/dev/null
grep -F 'systemd-networkd-resolve-hook.socket' "$upgrade_to_4" >/dev/null
pass "Omarchy 4 upgrade retires systemd-networkd for NetworkManager"