diff --git a/dns-switcher/README.md b/dns-switcher/README.md index 00fe714..9380cbb 100644 --- a/dns-switcher/README.md +++ b/dns-switcher/README.md @@ -75,8 +75,8 @@ neighbouring provider (what scroll sends). ## Requirements -- noctalia v5.0.0-beta.6 or newer for the core plugin; the gesture remapping - and lookup tester need a newer build still (`plugin_api = 22`) +- noctalia v5.0.0-beta.7 or newer (`plugin_api = 17`, for the `onExit` + lifecycle cleanup in `service.luau`) - NetworkManager (`networkmanager`, provides `nmcli`) with an active connection - Permission to modify system connections (see *Privileges* below) - `dig` (bind-tools/dnsutils) or `nslookup`, optional — only the lookup @@ -87,13 +87,19 @@ neighbouring provider (what scroll sends). *Privilege command* is **empty by default**: NetworkManager's polkit policy usually lets active local sessions modify system connections without a password. If you get a "not authorized" error, set it to `pkexec` (shows -noctalia's own polkit prompt) or `sudo -n` with a matching sudoers rule: +noctalia's own polkit prompt) or `sudo -n` with a matching sudoers rule. +The privilege command is applied to the `nmcli con mod` and `nmcli device +reapply` calls individually — never to a wrapping shell — so the sudoers +rule only ever needs to name `nmcli` itself: ``` # /etc/sudoers.d/nmcli-dns youruser ALL=(root) NOPASSWD: /usr/bin/nmcli ``` +With `pkexec`, this means an apply may show its polkit prompt twice (once +per elevated `nmcli` call) instead of once. + Or grant it via a polkit rule and keep the setting empty: ```js diff --git a/dns-switcher/dns-switcher.luau b/dns-switcher/dns-switcher.luau index 69a485f..2fae35b 100644 --- a/dns-switcher/dns-switcher.luau +++ b/dns-switcher/dns-switcher.luau @@ -13,7 +13,8 @@ -- binding that opens its own settings, and that default is exactly what's -- wanted here. -local STATE_KEY = "dns_state" +local STATE_KEY = "dns_state" -- published by service.luau + local GLYPH_UNKNOWN = "globe" local snapshot = nil -- last state published by the service diff --git a/dns-switcher/panel.luau b/dns-switcher/panel.luau index 2741d3c..03d5d36 100644 --- a/dns-switcher/panel.luau +++ b/dns-switcher/panel.luau @@ -4,8 +4,8 @@ -- "apply_request" entries this panel emits. Picking a provider applies it -- immediately (one nmcli change, no reactivation). -local STATE_KEY = "dns_state" -local REQUEST_KEY = "apply_request" +local STATE_KEY = "dns_state" -- published by service.luau +local REQUEST_KEY = "apply_request" -- consumed by service.luau local RESOLVE_TIMEOUT_MS = 4000 diff --git a/dns-switcher/plugin.toml b/dns-switcher/plugin.toml index fbe8193..a8c5a37 100644 --- a/dns-switcher/plugin.toml +++ b/dns-switcher/plugin.toml @@ -6,7 +6,7 @@ id = "nightwatch75/dns-switcher" name = "DNS Switcher" -version = "0.1.1" +version = "0.1.2" plugin_api = 17 author = "nightwatch75" license = "MIT" diff --git a/dns-switcher/service.luau b/dns-switcher/service.luau index 066ae92..20410ba 100644 --- a/dns-switcher/service.luau +++ b/dns-switcher/service.luau @@ -14,10 +14,13 @@ -- reapply pushes the change onto the live connection without reactivating -- it, so the network never drops. The privilege command is empty by default: -- NetworkManager's polkit policy lets active local sessions modify system --- connections on most desktop distros. +-- connections on most desktop distros. When set, it is prefixed onto each +-- of those two nmcli calls individually (never onto a wrapping shell), so a +-- sudoers NOPASSWD rule naming the nmcli binary itself is enough — see +-- apply() below and the README's Privileges section. -local STATE_KEY = "dns_state" -local REQUEST_KEY = "apply_request" +local STATE_KEY = "dns_state" -- published here, read by widget + panel +local REQUEST_KEY = "apply_request" -- sent by widget/panel, consumed here local BUILTIN = { { id = "google", label = "Google", ip = "8.8.8.8 8.8.4.4", glyph = "brand-google" }, @@ -52,10 +55,6 @@ local function trim(value) return (value:gsub("^%s+", ""):gsub("%s+$", "")) end -local function shellQuote(value) - return "'" .. value:gsub("'", "'\\''") .. "'" -end - local function pollSeconds() return math.max(2, tonumber(cfg("poll_seconds")) or 10) end @@ -302,23 +301,26 @@ local function apply(provider) else mods = 'ipv4.dns "' .. safeIp .. '" ipv4.ignore-auto-dns yes' end - local inner = 'ACT=$(LC_ALL=C nmcli -t -f TYPE,DEVICE,UUID connection show --active 2>/dev/null); ' - .. [[LINE=$(printf '%s\n' "$ACT" | grep -E '^(802-11-wireless|802-3-ethernet):' | head -n 1); ]] - .. [=[[ -n "$LINE" ] || LINE=$(printf '%s\n' "$ACT" | grep -v '^loopback:' | head -n 1); ]=] - .. [=[[ -n "$LINE" ] || exit 9; ]=] - .. 'DEV=$(printf \'%s\' "$LINE" | cut -d: -f2); ' - .. 'UUID=$(printf \'%s\' "$LINE" | cut -d: -f3); ' - .. 'nmcli con mod "$UUID" ' .. mods .. ' && nmcli device reapply "$DEV"' local priv = cfg("privilege_command") if type(priv) ~= "string" then priv = "" end priv = trim(priv) - local cmd = inner - if priv ~= "" then - cmd = priv .. " sh -c " .. shellQuote(inner) - end + -- Prefixed onto each nmcli invocation individually, never onto a + -- wrapping `sh -c`: the README's sudoers example authorizes the nmcli + -- binary itself (NOPASSWD: /usr/bin/nmcli), which never covers a shell + -- run under sudo. Discovering the device/uuid stays unprivileged either + -- way (it's a plain read), so only the two mutating calls need it. + local privPrefix = priv ~= "" and (priv .. " ") or "" + + local cmd = 'ACT=$(LC_ALL=C nmcli -t -f TYPE,DEVICE,UUID connection show --active 2>/dev/null); ' + .. [[LINE=$(printf '%s\n' "$ACT" | grep -E '^(802-11-wireless|802-3-ethernet):' | head -n 1); ]] + .. [=[[ -n "$LINE" ] || LINE=$(printf '%s\n' "$ACT" | grep -v '^loopback:' | head -n 1); ]=] + .. [=[[ -n "$LINE" ] || exit 9; ]=] + .. 'DEV=$(printf \'%s\' "$LINE" | cut -d: -f2); ' + .. 'UUID=$(printf \'%s\' "$LINE" | cut -d: -f3); ' + .. privPrefix .. 'nmcli con mod "$UUID" ' .. mods .. ' && ' .. privPrefix .. 'nmcli device reapply "$DEV"' changing = true publish() diff --git a/dns-switcher/thumbnail.webp b/dns-switcher/thumbnail.webp index b86d5cb..443af38 100644 Binary files a/dns-switcher/thumbnail.webp and b/dns-switcher/thumbnail.webp differ