diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 8445bb6..33efced 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -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 rebuild (like the background tint). - ✓ **Hibernate double-unlock:** on resume from hibernate the LUKS - passphrase already gates the machine, but hypridle's `before_sleep_cmd` - also locked hyprlock, so the user typed a password twice. Fixed with a - `nomarchy-hibernate-unlock` systemd unit (`modules/nixos/default.nix`) - that dismisses hyprlock *after* a hibernate resume (`WantedBy - hibernate.target`, `After systemd-hibernate.service` → runs post-resume), - gated on the disk being LUKS-encrypted. Suspend (and the RAM-resume phase - of suspend-then-hibernate) keep locking — they have no passphrase gate. + passphrase already gates the machine, but locking hyprlock before sleep + meant a second password. Fixed by *not locking* before an encrypted + hibernate: a `nomarchy-lock-before-sleep` systemd unit + (`modules/nixos/default.nix`) takes over from hypridle's `before_sleep_cmd` + and locks on the RAM-resume sleeps (suspend / hybrid-sleep / suspend-then- + hibernate) always, but skips `hibernate.target` when the disk is LUKS- + 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` (`nomarchy.keys.enable`): one agent — `services.gpg-agent` with `enableSshSupport` fronts SSH, so a single `pinentry-qt` (native-Wayland, diff --git a/modules/home/idle.nix b/modules/home/idle.nix index 62d7849..7a5eac1 100644 --- a/modules/home/idle.nix +++ b/modules/home/idle.nix @@ -64,11 +64,13 @@ in settings = { general = { lock_cmd = "pidof hyprlock || hyprlock"; - # Locks before every sleep. For suspend that's exactly right; on an - # encrypted hibernate the LUKS resume already gates the machine, so - # the system side dismisses this lock post-resume to avoid a double - # unlock — see nomarchy-hibernate-unlock in modules/nixos/default.nix. - before_sleep_cmd = "loginctl lock-session"; + # No before_sleep_cmd here: locking before sleep is driven + # system-side so it can fire for suspend but skip an encrypted + # hibernate — whose LUKS resume already gates the machine, so a + # hyprlock on top is a second password (and a Wayland session-lock + # 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"; }; listener = [ diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 43b6f5b..99d6a85 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -123,23 +123,32 @@ in networking.networkmanager.enable = lib.mkDefault true; - # No double-unlock on hibernate. hypridle locks hyprlock before every - # sleep (right for suspend), but a hibernate resume is already gated by - # the LUKS passphrase entered at boot — so the user would type a password - # twice. Once we're back from an *encrypted* hibernate, drop the now- - # redundant hyprlock. Ordered After the hibernate service, so it runs - # post-resume; pulled in only by hibernate.target (suspend, and the - # RAM-resume phase of suspend-then-hibernate, stay locked — they have no - # passphrase gate). Gated on LUKS: an unencrypted hibernate keeps its lock. - systemd.services.nomarchy-hibernate-unlock = - lib.mkIf (builtins.attrNames config.boot.initrd.luks.devices != [ ]) { - description = "Dismiss hyprlock after resuming from an encrypted hibernate"; - after = [ "systemd-hibernate.service" ]; - wantedBy = [ "hibernate.target" ]; + # No double-unlock on hibernate. Locking the session before sleep is + # right for suspend (resumes from RAM, no other gate), but an encrypted + # hibernate already resumes through the LUKS passphrase — a hyprlock on + # top is a second password. And we can't just drop the lock after the + # resume: a Wayland session-lock whose client dies without releasing + # trips hyprlock's "go to a tty" crash failsafe (the compositor keeps the + # screen locked for safety). So don't lock before an encrypted hibernate + # in the first place. hypridle can't tell suspend from hibernate, hence a + # system unit hooked to the sleep targets: it locks on the RAM-resume + # sleeps always, and on hibernate only when the disk is unencrypted (no + # LUKS gate to rely on). Replaces hypridle's old before_sleep_cmd. + systemd.services.nomarchy-lock-before-sleep = + 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 = { Type = "oneshot"; - # `-`: a missing hyprlock (idle disabled, or not locked) isn't a failure. - ExecStart = "-${pkgs.procps}/bin/pkill -x hyprlock"; + # Plural: a system unit has no session of its own to lock. + ExecStart = "${config.systemd.package}/bin/loginctl lock-sessions"; }; };