Automatically unlocking the gnome-keyring using LUKS key with greetd and hyprland

Hello there

I used to use GDM as my display manager, but I have had some issues with it, which is why I decided to try to switch go greetd, however I cannot for the life of me, figure out how I can make it auto unlock the gnome-keyring using the LUKS password used for disk decryption when logging in automatically. I had it working using GDM by creating a gdm-autologin file I will include the working configuration for GDM at the end. What I have in my new config so far is the following

{ pkgs, lib, config, ... }:
let
  tuigreet = "${pkgs.greetd.tuigreet}/bin/tuigreet";
  session = "${pkgs.hyprland}/bin/Hyprland";
  username = "wooptidoo";
in
{
  options = {
    desktop.enable = lib.mkEnableOption "enables libraries needed for desktop";
  };
  config = lib.mkIf config.desktop.enable {

    environment.systemPackages = with pkgs; [
      greetd.tuigreet
      libsecret
    ];
    services.xserver.enable = true;

    # Screensharing and stuff + portal for gnome-keyring
    xdg.portal = {
      enable = true;
      wlr.enable = true;
      extraPortals = with pkgs; [ 
        xdg-desktop-portal-gtk 
        xdg-desktop-portal-hyprland
      ];
    };

    # Enable WM and DM
    programs.hyprland.enable = true;
    services.xserver.desktopManager.gnome.enable = false;
    services.xserver.excludePackages = [ pkgs.xterm ]; 
    services.gnome.core-utilities.enable = false;
    services.gnome.rygel.enable = false;

    services.gnome.gnome-keyring.enable = true;
    services.dbus.packages = [ pkgs.gnome.seahorse ];

    boot.initrd.systemd.enable = true;
    security.pam.services.greetd = {
      enableGnomeKeyring = true;
    };

    services.greetd = {
      enable = true;
      settings = {
        initial_session = {
          command = "${session}";
          user = "${username}";
        };
        default_session = {
          command = "${tuigreet} --greeting 'Authorization required...' --asterisks --remember --remember-user-session --time --cmd ${session}";
          user = "greeter";
        };
      };
    };
  };
}

Everything except the automatic unlocking of the gnome-keyring works perfectly. If I don’t set the initial_session, and log in manually after disk decryption, the keyring is unlocked when entering Hyprland as I would expect.

As stated earlier I had this working with GDM, and I want to only enter my password once on boot, and preferably when decrypting my disk. Here is the config I used for GDM for anyone interested:

{ pkgs, lib, config, ... }:
{
  options = {
    desktop.enable = lib.mkEnableOption "enables libraries needed for desktop";
  };
  config = lib.mkIf config.desktop.enable {

    environment.systemPackages = with pkgs; [
      libsecret
    ];

    services.xserver.enable = true;
    xdg.portal = {
      enable = true;
      wlr.enable = true;
      extraPortals = with pkgs; [ 
        xdg-desktop-portal-gtk 
        xdg-desktop-portal-hyprland
      ];
    };

    # Enable WM and DM
    programs.hyprland.enable = true;
    services.xserver.desktopManager.gnome.enable = false;
    services.xserver.excludePackages = [ pkgs.xterm ]; 
    services.gnome.core-utilities.enable = false;
    services.gnome.rygel.enable = false;

    services.xserver.displayManager.gdm.enable = true;
    services.displayManager.autoLogin.enable = true;
    services.displayManager.autoLogin.user = "wooptidoo";
    services.gnome.gnome-keyring.enable = true;
    services.dbus.packages = [ pkgs.gnome.seahorse ];

    # Use the decryption passphrase to also unlock the gnome-keyring
    boot.initrd.systemd.enable = true;
    security.pam.services = {
      gdm-autologin.text = ''
        auth      requisite     pam_nologin.so

        auth      required      pam_succeed_if.so uid >= 1000 quiet
        auth      optional      ${pkgs.gnome.gdm}/lib/security/pam_gdm.so
        auth      optional      ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so
        auth      required      pam_permit.so

        account   sufficient    pam_unix.so

        password  requisite     pam_unix.so nullok yescrypt

        session   optional      pam_keyinit.so revoke
        session   include       login
      '';
    };
  };
}

I hope someone has some experience that can help me forward.

Best regards
Andreas Voss

1 Like

So these lines are the reason it works with GDM. When GDM does autologin, it uses the gdm-autologin PAM service. The pam_gdm module retrieves the LUKS passphrase from the kernel keyring and sets the PAM auth token to it (though, this is actually the same as what the pam_systemd_loadkey module would do; we just don’t use that anywhere right now I think), then the pam_gnome_keyring.so module launches the gnome-keyring-daemon and passes it the auth token to unlock the keyring.

So you’ll need greetd to be using a similar PAM service when doing autologin. I believe those same GDM pam modules will actually work in another PAM service, believe it or not. If greetd doesn’t also use a PAM service for autologin, then I think you’re out of luck and will have to do something custom.

1 Like

We have a PR to switch to pam_systemd_loadkey: nixos/gdm: enable pam_systemd_loadkey if available by jonathan-conder Ā· Pull Request #286587 Ā· NixOS/nixpkgs Ā· GitHub

1 Like

I tried to put something together based on that PR and some other information I found online, and ended up with this, however it still does not work, even though I think it should based on my (very limited) understanding. This article seems to match with what I am doing, and what is in the PR (which as far as I can tell is for GDM, which I am trying to adapt to greetd). Can you spot the issue with my approach?

systemd.services.greetd = {
  serviceConfig = {
    KeyringMode = lib.mkForce "inherit";
  };
};

services.gnome.gnome-keyring.enable = true;
services.dbus.packages = [ pkgs.gnome.seahorse ];
# Use the decryption passphrase to also unlock the gnome-keyring (which is currently broken with greetd)
boot.initrd.systemd.enable = true;

security.pam.services = let
  formatModuleArgument =
    token: if lib.hasInfix " " token then "[${lib.replaceStrings [ "]" ] [ "\\]" ] token}]" else token;

  keyname = formatModuleArgument "keyname=${config.desktop.autologin.keyname}";
  loadkeyArgs = lib.optionalString (config.desktop.autologin.keyname != null) " ${keyname}";
in
{
  greetd.text = ''
    # Account management.
    account required pam_unix.so # unix (order 10900)

    # Authentication management.
    auth optional pam_unix.so likeauth nullok # unix-early (order 11500)
    auth required ${config.systemd.package}/lib/security/pam_systemd_loadkey.so${loadkeyArgs}
    auth required ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so # gnome_keyring (order 12100)
    auth sufficient pam_unix.so likeauth nullok try_first_pass # unix (order 12800)
    auth required pam_deny.so # deny (order 13600)

    # Password management.
    password sufficient pam_unix.so nullok yescrypt # unix (order 10200)
    password optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so use_authtok # gnome_keyring (order 11200)

    # Session management.
    session required pam_env.so conffile=/etc/pam/environment readenv=0 # env (order 10100)
    session required pam_unix.so # unix (order 10200)
    session required pam_loginuid.so # loginuid (order 10300)
    session optional ${config.systemd.package}/lib/security/pam_systemd.so # systemd (order 12000)
    session optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start # gnome_keyring (order 12600)
  '';
};
2 Likes

Hey, did you ever sort this?

Unfortunately not no. I ended up accepting logging in twice on each boot for a while, and now I changed from greetd to sddm where I have not yet tried to get it to work, but at least now I have a pretty theme to look at when logging in the second time.

3 Likes

This can’t be (correctly) done until the Greetd PAM service includes/substacks the login PAM service. See this issue for more details - hopefully someone can prove me wrong. :wink:

Also, if pam_systemd_loadkey is used in pull/286587, we could potentially have an all purpose display manager module - decoupled from GDM or any specific DM.

That said - In the meantime, you can either leave your login keyring password blank or add the following to your home-manager config (where ā€œsomepassā€ is your password in plain-text):

{ pkgs, ... }:{
  programs.bash = {
    enable = true;
    profileExtra = ''
      echo -n "somepass" | gnome-keyring-daemon --unlock
    '';
  };
}

Obviously, neither of these temporary workarounds are ideal, and I’m not a security expert so I’m not sure which is the better option from a security standpoint.

Fair warnings aside, here’s my tuigreet.nix config for an automatic login (I’m sure some of this can be paired down - but it’s been a long four days to get to this point):

{ pkgs, lib, ... }:
let
  tuigreet = "${pkgs.greetd.tuigreet}/bin/tuigreet";
  session = "${pkgs.hyprland}/bin/Hyprland > /dev/null";
  username = "myUsername"; #change this to your username  
in
{
  services.gnome.gnome-keyring.enable = lib.mkDefault true; 
  programs.seahorse.enable = true; 
  security.pam.services = {
    greetd.enableGnomeKeyring = true;
    login.enableGnomeKeyring = true;
  };
  services.dbus.packages = [ pkgs.gnome-keyring pkgs.gcr ];

  services.greetd = {
    enable = true;
    settings = {
      initial_session = {
        command = "${session}";
        user = "${username}";
      };
      default_session = {
        command = # --remember-user-session
        "${tuigreet} --remember --asterisks --container-padding 2 --time --time-format '%I:%M %p | %a • %h | %F' --cmd '${session}' --theme 'container=darkgray;border=lightblue'";
        user = "greeter";
      };
    };
  };
}

Good luck, brave travelers. - GM

1 Like

For me, this doesn’t work :frowning:
I still get prompted for a password when I open a GTK app

@cccslater, I can help troubleshoot with a few questions:

  1. is the ā€œecho -n ā€œsomepassā€ | gnome-keyring-daemon --unlockā€ line making it into your ~/.profile file?
  2. Are you using bash?
  3. Immediately after logging in, does seahorse show a locked or unlocked padlock, or no padlock at all?
  4. Does journalctl -b | grep gnome-keyring have any helpful info? And does ps aux | grep gnome-keyring-daemon show that the gnome-keyring-daemon is running on boot prior to opening a GTK app?
  5. Which GTK app are you opening? I can try reproducing on my end.
  6. In your configuration.nix, do you have environment.sessionVariables = { XDG_RUNTIME_DIR = "/run/user/$UID"; }; set?

Went through all the steps, typed out a huuuge message, then noticed --unlock is for the login keyring. My login keyring has never worked properly – I added login.enableGnomeKeyring = true; and now I can unlock the login keyring (in Seahorse).

I managed to unlock from the command line once or twice, but can’t get it working again!

I think the issue is my login keyring keeps emptying itself somehow. I’ve deleted it a few times to try and start it again but I can’t work out how to populate it properly.

OK, with a lot of back and forth and fiddling, I now have a working keyring. Thanks so much for the tips, @guttermonk!

1 Like

I just got tuigreet auto-unlock working (running nixos 25.11). Credit goes to Ivan Brennan for posting this on sourcehut, and to Tim Ruffing for making the connection between the original message on sourcehut and this thread and for letting me know.

Requirements:

  • Requires boot.initrd.systemd.enable = true
  • Requires LUKS encryption
  • User password must match LUKS password

Here is my tuigreet-autounlock.nix

{ pkgs, lib, ... }:

let
  tuigreetBin = "${pkgs.tuigreet}/bin/tuigreet";
  session = "start-hyprland > /dev/null";
  username = "youruser";  # Change this to your username
  
  # Theme colors and spacing
  containerColor = "darkgray";
  borderColor = "lightblue";
  containerPadding = "3";
  timeFormat = "'%I:%M %p | %a • %h | %F'";
  
  # Package for pam_fde_boot_pw - retrieves LUKS password from systemd
  # https://git.sr.ht/~kennylevinsen/pam_fde_boot_pw
  pam_fde_boot_pw = pkgs.stdenv.mkDerivation {
    pname = "pam_fde_boot_pw";
    version = "0.1.0";
    
    src = pkgs.fetchzip {
      url = "https://git.sr.ht/~kennylevinsen/pam_fde_boot_pw/archive/master.tar.gz";
      sha256 = "sha256-dS9ufryg3xfxgUzJKDgrvMZP2qaYH+WJQFw1ogl1isc=";
    };
    
    nativeBuildInputs = [ pkgs.pkg-config pkgs.meson pkgs.ninja ];
    buildInputs = [ pkgs.pam pkgs.systemd pkgs.keyutils ];
  };
in
{

    environment.systemPackages = with pkgs; [
      tuigreet
      libsecret
      gnome-keyring
      libgnome-keyring
    ];

    # CRITICAL: Required for pam_fde_boot_pw to work
    # Stores the LUKS password in systemd so it can be retrieved later
    boot.initrd.systemd.enable = true;

    # Enable gnome-keyring service
    services.gnome.gnome-keyring.enable = true;
    
    # Enable the graphical frontend for managing keyring
    programs.seahorse.enable = true;

    # PAM Configuration
    # The key insight: greetd's initial_session (autologin) doesn't call PAM auth,
    # so we inject the LUKS password during the session phase instead using pam_fde_boot_pw.
    # See: https://lists.sr.ht/~kennylevinsen/greetd-devel/%3CCAOVAYzup8rEVtq1q4Bw5jZS=tf1WyeWwhHB0jgHvoZyhUuGZeg@mail.gmail.com%3E
    security.pam.services.greetd = {
      enableGnomeKeyring = true;
      
      # Add pam_fde_boot_pw rule BEFORE gnome_keyring in the session phase
      # This ensures the LUKS password is injected before gnome-keyring tries to unlock
      # Order 12600: gnome_keyring is typically at 12700, so this runs before it
      rules.session.fde_boot_pw = {
        order = 12600;
        enable = true;
        control = "optional";
        modulePath = "${pam_fde_boot_pw}/lib/security/pam_fde_boot_pw.so";
        args = [ "inject_for=gkr" ];
      };
    };
    
    # Enable gnome-keyring for other PAM services
    security.pam.services = {
      greetd-password.enableGnomeKeyring = true;
      login.enableGnomeKeyring = true;
      gdm-password.enableGnomeKeyring = true;
    };

    # greetd configuration
    services.greetd = {
      enable = true;
      settings = {
        # initial_session is used for autologin
        initial_session = {
          command = "${session}";
          user = "${username}";
        };
        # default_session is used for manual login
        default_session = {
          command = "${tuigreetBin} --remember --asterisks --container-padding ${containerPadding} --time --time-format ${timeFormat} --cmd '${session}' --theme 'container=${containerColor};border=${borderColor}'";
          user = "greeter";
        };
      };
    };

    # greetd systemd service configuration
    # CRITICAL for autologin to work properly
    # https://www.reddit.com/r/NixOS/comments/u0cdpi/tuigreet_with_xmonad_how/
    systemd.services.greetd.serviceConfig = {
      Type = "idle";  # DO NOT CHANGE - "simple" breaks autologin!
      StandardInput = "tty";
      StandardOutput = "tty";
      StandardError = "journal";
      TTYReset = true;
      TTYVHangup = true;
      TTYVTDisallocate = true;
      KeyringMode = lib.mkForce "inherit";
    };

    # Disable getty@tty1 to prevent TTY interference
    # https://github.com/NixOS/nixpkgs/issues/103746
    systemd.services."getty@tty1".enable = false;
    systemd.services."autovt@tty1".enable = false;

    # SSH agent configuration
    programs.ssh.startAgent = false;
    environment.sessionVariables = {
      SSH_AUTH_SOCK = "/run/user/1000/gcr/ssh";
    };
}

How It Works

  1. You unlock LUKS at boot → systemd stores the password
  2. greetd starts initial_session (autologin, no PAM auth)
  3. PAM session phase runs → pam_fde_boot_pw retrieves LUKS password
  4. pam_gnome_keyring uses that password to unlock your keyring
  5. You’re logged in with unlocked keyring → no password prompts!
5 Likes

When greetd performs auto-login, it skips the PAM auth stack altogether, so neither pam_systemd_loadkey nor pam_gdm are effective. Instead, you can use pam_fde_boot_pw (written by the author of greetd), which ties into the PAM session stack.

I created a PR to add it to nixpkgs:

The PR description includes a usage example, or you can see at how I’m using it in my nixos config.

It would be nice to provide services.greetd options that make this easier to set up, but I’m planning to tackle that in a separate PR.

3 Likes

Surprised by this – my impression was that the login keyring password must match the LUKS password, not my actual user account password?

I was initially setting KeyringMode to inherit too, but found that shared works as well.

The pam_fde_boot_pw README mentions,

you may need to specify KeyringMode=inherit or KeyringMode=shared

I’ve been using shared since, but mostly because that’s what the existing NixOS module for greetd uses. I don’t know which of inherit or shared is truly the best choice in this case.