fix(idle): don't lock before an encrypted hibernate (was: unlock after)

The previous nomarchy-hibernate-unlock ran `pkill hyprlock` after a
hibernate resume, but killing a Wayland session-lock client without
releasing the lock trips hyprlock's "go to a tty" crash failsafe -- the
compositor keeps the screen locked for safety. So resume showed a hyprlock
error screen instead of unlocking.

Fix it the right way: never engage the lock before an encrypted hibernate
(the LUKS passphrase at resume is the gate). Replace the post-resume unlock
with a nomarchy-lock-before-sleep unit that takes over hypridle's
before_sleep_cmd: it locks on the RAM-resume sleeps (suspend / hybrid-sleep
/ suspend-then-hibernate) always, and on hibernate.target only when the disk
is unencrypted. idle.nix drops before_sleep_cmd accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Bernardo Magri
2026-06-15 10:15:31 +01:00
parent a606c0d498
commit 1b0eeeaf6c
3 changed files with 40 additions and 27 deletions

View File

@@ -131,13 +131,15 @@ how to override it. Items marked ✓ are shipped.
accent. Reads on light and dark; follows the theme as of the last system accent. Reads on light and dark; follows the theme as of the last system
rebuild (like the background tint). rebuild (like the background tint).
-**Hibernate double-unlock:** on resume from hibernate the LUKS -**Hibernate double-unlock:** on resume from hibernate the LUKS
passphrase already gates the machine, but hypridle's `before_sleep_cmd` passphrase already gates the machine, but locking hyprlock before sleep
also locked hyprlock, so the user typed a password twice. Fixed with a meant a second password. Fixed by *not locking* before an encrypted
`nomarchy-hibernate-unlock` systemd unit (`modules/nixos/default.nix`) hibernate: a `nomarchy-lock-before-sleep` systemd unit
that dismisses hyprlock *after* a hibernate resume (`WantedBy (`modules/nixos/default.nix`) takes over from hypridle's `before_sleep_cmd`
hibernate.target`, `After systemd-hibernate.service` → runs post-resume), and locks on the RAM-resume sleeps (suspend / hybrid-sleep / suspend-then-
gated on the disk being LUKS-encrypted. Suspend (and the RAM-resume phase hibernate) always, but skips `hibernate.target` when the disk is LUKS-
of suspend-then-hibernate) keep locking — they have no passphrase gate. encrypted. (A first attempt that dismissed hyprlock *after* resume was
wrong — killing a Wayland session-lock client trips its "go to a tty"
crash failsafe instead of unlocking, which is the error screen it caused.)
-**Key agents & pinentry:** ships `modules/home/keys.nix` -**Key agents & pinentry:** ships `modules/home/keys.nix`
(`nomarchy.keys.enable`): one agent — `services.gpg-agent` with (`nomarchy.keys.enable`): one agent — `services.gpg-agent` with
`enableSshSupport` fronts SSH, so a single `pinentry-qt` (native-Wayland, `enableSshSupport` fronts SSH, so a single `pinentry-qt` (native-Wayland,

View File

@@ -64,11 +64,13 @@ in
settings = { settings = {
general = { general = {
lock_cmd = "pidof hyprlock || hyprlock"; lock_cmd = "pidof hyprlock || hyprlock";
# Locks before every sleep. For suspend that's exactly right; on an # No before_sleep_cmd here: locking before sleep is driven
# encrypted hibernate the LUKS resume already gates the machine, so # system-side so it can fire for suspend but skip an encrypted
# the system side dismisses this lock post-resume to avoid a double # hibernate — whose LUKS resume already gates the machine, so a
# unlock — see nomarchy-hibernate-unlock in modules/nixos/default.nix. # hyprlock on top is a second password (and a Wayland session-lock
before_sleep_cmd = "loginctl lock-session"; # can't be safely dropped after the fact — killing the locker trips
# its "go to a tty" crash failsafe). See nomarchy-lock-before-sleep
# in modules/nixos/default.nix.
after_sleep_cmd = "hyprctl dispatch dpms on"; after_sleep_cmd = "hyprctl dispatch dpms on";
}; };
listener = [ listener = [

View File

@@ -123,23 +123,32 @@ in
networking.networkmanager.enable = lib.mkDefault true; networking.networkmanager.enable = lib.mkDefault true;
# No double-unlock on hibernate. hypridle locks hyprlock before every # No double-unlock on hibernate. Locking the session before sleep is
# sleep (right for suspend), but a hibernate resume is already gated by # right for suspend (resumes from RAM, no other gate), but an encrypted
# the LUKS passphrase entered at boot — so the user would type a password # hibernate already resumes through the LUKS passphrase — a hyprlock on
# twice. Once we're back from an *encrypted* hibernate, drop the now- # top is a second password. And we can't just drop the lock after the
# redundant hyprlock. Ordered After the hibernate service, so it runs # resume: a Wayland session-lock whose client dies without releasing
# post-resume; pulled in only by hibernate.target (suspend, and the # trips hyprlock's "go to a tty" crash failsafe (the compositor keeps the
# RAM-resume phase of suspend-then-hibernate, stay locked — they have no # screen locked for safety). So don't lock before an encrypted hibernate
# passphrase gate). Gated on LUKS: an unencrypted hibernate keeps its lock. # in the first place. hypridle can't tell suspend from hibernate, hence a
systemd.services.nomarchy-hibernate-unlock = # system unit hooked to the sleep targets: it locks on the RAM-resume
lib.mkIf (builtins.attrNames config.boot.initrd.luks.devices != [ ]) { # sleeps always, and on hibernate only when the disk is unencrypted (no
description = "Dismiss hyprlock after resuming from an encrypted hibernate"; # LUKS gate to rely on). Replaces hypridle's old before_sleep_cmd.
after = [ "systemd-hibernate.service" ]; systemd.services.nomarchy-lock-before-sleep =
wantedBy = [ "hibernate.target" ]; let
encrypted = builtins.attrNames config.boot.initrd.luks.devices != [ ];
# Sleeps whose normal resume is from RAM — always lock these.
ramTargets = [ "suspend.target" "hybrid-sleep.target" "suspend-then-hibernate.target" ];
ramServices = [ "systemd-suspend.service" "systemd-hybrid-sleep.service" "systemd-suspend-then-hibernate.service" ];
in
{
description = "Lock the session before sleep (skipped for an encrypted hibernate)";
before = ramServices ++ lib.optional (!encrypted) "systemd-hibernate.service";
wantedBy = ramTargets ++ lib.optional (!encrypted) "hibernate.target";
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
# `-`: a missing hyprlock (idle disabled, or not locked) isn't a failure. # Plural: a system unit has no session of its own to lock.
ExecStart = "-${pkgs.procps}/bin/pkill -x hyprlock"; ExecStart = "${config.systemd.package}/bin/loginctl lock-sessions";
}; };
}; };