Privilege prompts and the keyring
Mount a disk from the file manager, change a network setting, open a printer queue: without this slot every privileged GUI action fails for want of someone to ask the password, and without the keyring every app asks for its secrets again after each login. Both come on with the profile: a polkit agent to ask, a keyring to remember.
programs.scoot.desktop = { enable = true; # No privilege prompts on this box (privileged actions refuse # naming the missing agent, as below): # auth.enable = false; # No keyring on this box (every app re-prompts for secrets): # secrets.enable = false; # A different agent (default polkit-gnome): # auth.daemon = "lxqt";};What runs: the login entry names the agent (SCOOT_POLKIT_AGENT
in its Exec line), and the session leader spawns it once the
display is known. It is supervised, so a dead agent restarts after two
seconds, and it stops with the session. It runs as a child of the
leader because a polkit agent registers against its own logind session,
and a systemd user unit never joins one (the unit’s agent stays
connected but unregistered, and registration fails with “User of caller
and user of subject differs”). The agent holds nothing when no prompt
is open: it blocks on D-Bus, keeps no surface, and wakes nothing
(measured below). With the prompt off the entry is the plain launcher,
and privileged actions refuse with polkit’s own no-agent error. A
hand-written entry (a set session. replaces the whole line)
carries the agent by setting SCOOT_POLKIT_AGENT itself, to the
agent’s absolute path.
The keyring runs nothing until asked: gnome-keyring owns
org. through D-Bus activation (its service files
start the daemon --components=secrets on the first call, and it
holds the unlocked keyring until the session ends), with
secret-tool (from libsecret) on PATH for scripts and terminals:
secret-tool store --label='wifi' ssid MyNetwork # any login...secret-tool lookup ssid MyNetwork # ...reads back, no promptOn a greetd login the login password unlocks the keyring during login (below), so the first use, and every later one until logout, is seamless. Off the greetd path the first use of each login performs the one unlock (a graphical app prompts; the CLI refuses until unlocked).
Polkit agents, measured at the pinned rev (8ce4ef6, aarch64-linux,
all on the M2): full closures by nix path-info --closure-size,
marginals as new store paths over the profile’s own tools, idle RSS as
VmRSS under Xvfb, wakeups as context-switch deltas over 60 s idle:
| Tool | Version | Full closure | New over profile | Idle RSS | Wakeups idle | Why / why not |
|---|---|---|---|---|---|---|
| polkit-gnome | 0.105 | 335.9 MiB | 403 KiB (1 path: itself) | ~4.1 MB | 0 in 60 s | the pick: its whole GTK stack already rides with the portal backend, so the marginal is the agent alone; the oldest and most boring of the three |
| lxqt-policykit | 2.4.0 | 858.9 MiB | 385.9 MiB (39 paths) | ~4.1 MB | 0 in 60 s | a second toolkit (Qt6: qtdeclarative alone is 195 MB) for the same prompt |
| hyprpolkitagent | 0.1.3 | 1.7 GiB | 642.9 MiB (71 paths) | ~4.2 MB | 0 in 60 s | Qt6 plus KDE (kirigami, breeze-icons, ffmpeg) at ~1600× the marginal — and the youngest codebase of the three |
Every agent idles at zero wakeups (all three block on D-Bus, none polls), so the pick is closure, not CPU: polkit-gnome’s marginal is three orders of magnitude smaller than either Qt agent’s.
Keyrings, same method:
| Tool | Version | Full closure | New over profile | Idle RSS | Wakeups idle | Why / why not |
|---|---|---|---|---|---|---|
| gnome-keyring | 50.0 | 412.6 MiB | 34.6 MiB (11 paths) | ~4.1 MB | 0 in 60 s | the pick: D-Bus activated (no unit, starts on first use), and the only candidate the login password unlocks (below) |
| KeePassXC | 2.7.12 | 542.4 MiB | 169.5 MiB (26 paths) | — | — | needs its full GUI app running with the database open: a second toolkit (Qt5) for a second prompt by design — no PAM unlock path exists |
The keyring daemon starts on the first secrets call and persists only because an unlocked keyring must live somewhere. The polkit agent cannot be activated that way (polkitd needs one registered before the prompt), so the session leader spawns it once the display is known.
Unlocking takes no extra password entry on a greetd login. The
module adds two PAM rules confined to greetd’s own service (an auth
rule caching the login password beside its login substack, and a
session rule with auto_start, each ten past it), and a password
login through ReGreet unlocks there (the greeter log says gkr-pam: gnome-keyring-daemon started properly and unlocked keyring). The
unlocked daemon lands in the user’s session scope, not the
greeter’s, and stays there until logout: pam_systemd runs inside the
login substack, ahead of the keyring rule, so the worker is already in
the new scope before the daemon forks. The first secrets use finds it
through the control socket at its default path
($XDG_RUNTIME_DIR/keyring/control): D-Bus activation runs
gnome-keyring-daemon --start, which hands the bus name to the
already-running unlocked daemon and exits, so no second daemon ever
serves.
The module wires the daemon, bus files and IPC-lock wrapper directly
rather than through nixpkgs’ flags. Nixpkgs’ enableGnomeKeyring is a
silent no-op on greetd’s service (it lives inside the default-rules
block, and greetd sets useDefaultRules = false), and the stock
services. owns the login service’s PAM,
which would broaden the change past greetd.
What else prompts: anything that never puts the password through greetd’s PAM (a TTY login, an SSH login, an autologin with no password at all, or a login screen that is not greetd), and a login password that differs from the keyring’s (one changed without the other). The keyring never blocks the session: a locked keyring means prompts, not a failure.
With neither half, failures stay loud: with no agent, a privileged
action refuses with polkit’s own error naming the missing agent
(pkexec says no agent can authenticate); with no keyring,
secret-tool lookup fails naming the missing bus name. An SSH session
sharing the user manager gets the same refusal (no agent is registered
for its session). The agent dying mid-prompt fails that one prompt (the
client sees the dismissal); the leader restarts it in two seconds and
the next prompt works, so retry it.
Every value is an option. A rebuild/switch re-renders the session entry, but the running agent is the session’s own child, so a change to the agent takes effect at the next login:
| Option | Type | Default | Meaning |
|---|---|---|---|
desktop. |
bool | true with the profile |
name the agent in the session entry and run the authority beside it |
desktop. |
enum ("gnome", "lxqt", "hyprpolkit") |
"gnome" |
the agent behind enable (a future scoot agent widens this without renaming anything) |
desktop. |
package or null | per-daemon (Linux-only: null off Linux) | point at your own agent build; null with the switch on fails evaluation naming it |
desktop. |
bool | true with the profile |
publish the keyring’s bus names, install daemon and client, unlock on greetd logins |
desktop. |
enum ("gnome-keyring") |
"gnome-keyring" |
the daemon behind enable (a future scoot keyring widens this without renaming anything) |
desktop. / . |
package or null | gnome-keyring / libsecret (Linux-only: null off Linux) | point at your own builds; null with the switch on fails evaluation naming it |
Troubleshooting, by symptom:
- A privileged action says no authentication agent. The leader never
spawned it:
pgrep -a polkit-gnome(or the lxqt/hypr binary for those daemons) empty means the prompt has nobody to ask. Check the entry carriesSCOOT_POLKIT_AGENT(grep SCOOT_POLKIT_AGENT /run/current-system/sw/share/wayland-sessions/scoot.), thendesktop loginctl(an SSH session has no agent by design; run the action from the graphical session). Withauth.this is the expected refusal, not an error.enable = false - The prompt appears but the password never works. The agent shows
the dialog, polkitd decides: check the action’s policy
(
pkaction --verbose), and that the user is in the right group. - The keyring asks every login. On a greetd login the login
password unlocks during login, and the
unlock holds until logout. If a use still prompts, the priming
never happened (
journalctlshows nogkr-pamline for the login) and the PAM pair never applied:grep gnome_keyring /etc/pam.should show two lines. Off the greetd path (TTY, SSH, autologin, another greeter) the first secrets use of each login unlocks once (the login password), then stays unlocked till logout.d/greetd secret-tool lookupsays no such service. The bus names never published:secrets.without the NixOS side (a home-manager-only setup publishes nothing, because the activation files are system files).enable busctl --user list | grep -i secretis empty until the first use wakes the daemon.- Two prompts stack. Another agent runs beside this one (a desktop
environment’s own, or a hand-started one): only one agent per
session should register. Turn this one off
(
auth.) or stop the other.enable = false - Two keyrings fight. Another secrets implementation owns the bus
name (KeePassXC’s secret-service with its app open, another
gnome-keyring): whoever owns
org.answers.freedesktop. secrets busctl --user status org.names the owner.freedesktop. secrets
