feat(tailscale): operator-set login user — VPN menu skips the sudo terminal
nomarchy.services.tailscale now makes the login user the Tailscale operator (services.tailscale.extraSetFlags = ["--operator=<username>"], mkDefault), so `tailscale up/down/set` — and the System → VPN menu's Tailscale controls — run without sudo. The user is already in wheel, so the operator grant is no real new privilege, just skips the prompt. nomarchy-vpn's Tailscale actions are now operator-aware: a ts_priv helper runs the subcommand inline and only falls back to a sudo terminal if the operator grant is absent (downstream dropped it). Connect brings an authed-but-down node up inline; a node that still needs interactive login goes to a terminal so the auth URL is visible. Option description + template comment updated; ROADMAP follow-up closed. System + home builds green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -446,9 +446,10 @@ ${themeRows}
|
||||
# up/down (networkmanager-group users need no sudo).
|
||||
# · Import a WireGuard .conf or OpenVPN .ovpn (nmcli import; OpenVPN needs the
|
||||
# networkmanager-openvpn plugin, shipped system-side).
|
||||
# · Tailscale (self-gated on the CLI being present = nomarchy.services.tailscale):
|
||||
# status is read-only; up/down/exit-node need root, so they go via a
|
||||
# terminal + sudo (the snapshot-tool pattern), no operator wiring needed.
|
||||
# · Tailscale (self-gated on the CLI being present = nomarchy.services.tailscale,
|
||||
# which makes the login user the operator): status is read-only; up/down/
|
||||
# exit-node run inline without sudo thanks to the operator grant, falling
|
||||
# back to a sudo terminal if it's absent. First login goes to a terminal.
|
||||
# Secrets stay in NetworkManager's / Tailscale's own store — no new manager.
|
||||
nomarchy-vpn = pkgs.writeShellScriptBin "nomarchy-vpn" ''
|
||||
set -u
|
||||
@@ -485,12 +486,19 @@ ${themeRows}
|
||||
}
|
||||
|
||||
ts_state() { tailscale status --json 2>/dev/null | jq -r '.BackendState // "Unknown"'; }
|
||||
# Run a privileged tailscale subcommand. nomarchy.services.tailscale makes
|
||||
# the login user the operator, so this runs inline (no sudo); if the operator
|
||||
# grant is absent (downstream dropped it) it falls back to a sudo terminal.
|
||||
ts_priv() {
|
||||
if tailscale "$@" >/dev/null 2>&1; then notify-send "Tailscale" "tailscale $*"
|
||||
else ${cfg.terminal} -e sudo tailscale "$@"; fi
|
||||
}
|
||||
ts_exit_node() {
|
||||
node=$( { printf 'none\n'; tailscale exit-node list 2>/dev/null | awk 'NR>1 && $2 {print $2}'; \
|
||||
printf '%s\n' "$BACK"; } | rofi -dmenu -p "Exit node" ) || return
|
||||
{ [ -z "$node" ] || [ "$node" = "$BACK" ]; } && return
|
||||
[ "$node" = none ] && node=""
|
||||
${cfg.terminal} -e sudo tailscale set --exit-node="$node"
|
||||
ts_priv set --exit-node="$node"
|
||||
}
|
||||
tailscale_menu() {
|
||||
while :; do
|
||||
@@ -502,8 +510,10 @@ ${themeRows}
|
||||
} | rofi -dmenu -p "Tailscale ($st)" ) || return
|
||||
case "$choice" in
|
||||
"$BACK"|"") return ;;
|
||||
Connect) ${cfg.terminal} -e sudo tailscale up ;;
|
||||
Disconnect) ${cfg.terminal} -e sudo tailscale down ;;
|
||||
# Authed-but-down → bring it up inline; otherwise it needs an
|
||||
# interactive login, so show it in a terminal (URL visible).
|
||||
Connect) if [ "$st" = Stopped ]; then ts_priv up; else ${cfg.terminal} -e sudo tailscale up; fi ;;
|
||||
Disconnect) ts_priv down ;;
|
||||
"Exit node…") ts_exit_node ;;
|
||||
esac
|
||||
done
|
||||
|
||||
@@ -11,8 +11,9 @@ in
|
||||
{
|
||||
options.nomarchy.services = {
|
||||
tailscale.enable = lib.mkEnableOption ''
|
||||
Tailscale, the mesh VPN — ships the daemon; authenticate once with
|
||||
`sudo tailscale up`'';
|
||||
Tailscale, the mesh VPN — ships the daemon and makes the login user its
|
||||
operator, so `tailscale up/down/set` and the System → VPN menu work
|
||||
without sudo. Authenticate once (the menu's Connect, or `tailscale up`)'';
|
||||
|
||||
syncthing.enable = lib.mkEnableOption ''
|
||||
Syncthing continuous file sync, running as the login user — add
|
||||
@@ -125,6 +126,12 @@ in
|
||||
config = lib.mkMerge [
|
||||
(lib.mkIf cfg.tailscale.enable {
|
||||
services.tailscale.enable = true;
|
||||
# Let the login user drive tailscale (up/down/set — and so the VPN menu's
|
||||
# Tailscale controls) without sudo. It's already in wheel, so the operator
|
||||
# grant is no real new privilege, just skips the password prompt. The
|
||||
# module's tailscaled-set unit applies this after the daemon starts.
|
||||
# mkDefault so a downstream can drop or replace it (e.g. extraSetFlags = []).
|
||||
services.tailscale.extraSetFlags = lib.mkDefault [ "--operator=${args.username}" ];
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.syncthing.enable {
|
||||
|
||||
Reference in New Issue
Block a user