1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 10 cfg = config.services.xserver.displayManager; 11 gdm = pkgs.gdm; 12 pamLogin = config.security.pam.services.login; 13 settingsFormat = pkgs.formats.ini { }; 14 configFile = settingsFormat.generate "custom.conf" cfg.gdm.settings; 15 16 xSessionWrapper = 17 if (cfg.setupCommands == "") then 18 null 19 else 20 pkgs.writeScript "gdm-x-session-wrapper" '' 21 #!${pkgs.bash}/bin/bash 22 ${cfg.setupCommands} 23 exec "$@" 24 ''; 25 26 # Solves problems like: 27 # https://wiki.archlinux.org/index.php/Talk:Bluetooth_headset#GDMs_pulseaudio_instance_captures_bluetooth_headset 28 # Instead of blacklisting plugins, we use Fedora's PulseAudio configuration for GDM: 29 # https://src.fedoraproject.org/rpms/gdm/blob/master/f/default.pa-for-gdm 30 pulseConfig = pkgs.writeText "default.pa" '' 31 load-module module-device-restore 32 load-module module-card-restore 33 load-module module-udev-detect 34 load-module module-native-protocol-unix 35 load-module module-default-device-restore 36 load-module module-always-sink 37 load-module module-intended-roles 38 load-module module-suspend-on-idle 39 load-module module-position-event-sounds 40 ''; 41 42 defaultSessionName = config.services.displayManager.defaultSession; 43 44 setSessionScript = pkgs.callPackage ./account-service-util.nix { }; 45in 46 47{ 48 imports = [ 49 (lib.mkRenamedOptionModule 50 [ "services" "xserver" "displayManager" "gdm" "autoLogin" "enable" ] 51 [ 52 "services" 53 "displayManager" 54 "autoLogin" 55 "enable" 56 ] 57 ) 58 (lib.mkRenamedOptionModule 59 [ "services" "xserver" "displayManager" "gdm" "autoLogin" "user" ] 60 [ 61 "services" 62 "displayManager" 63 "autoLogin" 64 "user" 65 ] 66 ) 67 68 (lib.mkRemovedOptionModule [ 69 "services" 70 "xserver" 71 "displayManager" 72 "gdm" 73 "nvidiaWayland" 74 ] "We defer to GDM whether Wayland should be enabled.") 75 ]; 76 77 meta = { 78 maintainers = lib.teams.gnome.members; 79 }; 80 81 ###### interface 82 83 options = { 84 85 services.xserver.displayManager.gdm = { 86 87 enable = lib.mkEnableOption "GDM, the GNOME Display Manager"; 88 89 debug = lib.mkEnableOption "debugging messages in GDM"; 90 91 # Auto login options specific to GDM 92 autoLogin.delay = lib.mkOption { 93 type = lib.types.int; 94 default = 0; 95 description = '' 96 Seconds of inactivity after which the autologin will be performed. 97 ''; 98 }; 99 100 wayland = lib.mkOption { 101 type = lib.types.bool; 102 default = true; 103 description = '' 104 Allow GDM to run on Wayland instead of Xserver. 105 ''; 106 }; 107 108 autoSuspend = lib.mkOption { 109 default = true; 110 description = '' 111 On the GNOME Display Manager login screen, suspend the machine after inactivity. 112 (Does not affect automatic suspend while logged in, or at lock screen.) 113 ''; 114 type = lib.types.bool; 115 }; 116 117 banner = lib.mkOption { 118 type = lib.types.nullOr lib.types.lines; 119 default = null; 120 example = '' 121 foo 122 bar 123 baz 124 ''; 125 description = '' 126 Optional message to display on the login screen. 127 ''; 128 }; 129 130 settings = lib.mkOption { 131 type = settingsFormat.type; 132 default = { }; 133 example = { 134 debug.enable = true; 135 }; 136 description = '' 137 Options passed to the gdm daemon. 138 See [here](https://help.gnome.org/admin/gdm/stable/configuration.html.en#daemonconfig) for supported options. 139 ''; 140 }; 141 142 }; 143 144 }; 145 146 ###### implementation 147 148 config = lib.mkIf cfg.gdm.enable { 149 150 services.xserver.displayManager.lightdm.enable = false; 151 152 users.users.gdm = { 153 name = "gdm"; 154 uid = config.ids.uids.gdm; 155 group = "gdm"; 156 home = "/run/gdm"; 157 description = "GDM user"; 158 }; 159 160 users.groups.gdm.gid = config.ids.gids.gdm; 161 162 # GDM needs different xserverArgs, presumable because using wayland by default. 163 services.xserver.tty = null; 164 services.xserver.display = null; 165 services.xserver.verbose = null; 166 167 services.displayManager = { 168 # Enable desktop session data 169 enable = true; 170 171 environment = 172 { 173 GDM_X_SERVER_EXTRA_ARGS = toString (lib.filter (arg: arg != "-terminate") cfg.xserverArgs); 174 XDG_DATA_DIRS = lib.makeSearchPath "share" [ 175 gdm # for gnome-login.session 176 config.services.displayManager.sessionData.desktops 177 pkgs.gnome-control-center # for accessibility icon 178 pkgs.adwaita-icon-theme 179 pkgs.hicolor-icon-theme # empty icon theme as a base 180 ]; 181 } 182 // lib.optionalAttrs (xSessionWrapper != null) { 183 # Make GDM use this wrapper before running the session, which runs the 184 # configured setupCommands. This relies on a patched GDM which supports 185 # this environment variable. 186 GDM_X_SESSION_WRAPPER = "${xSessionWrapper}"; 187 }; 188 execCmd = "exec ${gdm}/bin/gdm"; 189 preStart = lib.optionalString (defaultSessionName != null) '' 190 # Set default session in session chooser to a specified values basically ignore session history. 191 ${setSessionScript}/bin/set-session ${config.services.displayManager.sessionData.autologinSession} 192 ''; 193 }; 194 195 systemd.tmpfiles.rules = 196 [ 197 "d /run/gdm/.config 0711 gdm gdm" 198 ] 199 ++ lib.optionals config.services.pulseaudio.enable [ 200 "d /run/gdm/.config/pulse 0711 gdm gdm" 201 "L+ /run/gdm/.config/pulse/${pulseConfig.name} - - - - ${pulseConfig}" 202 ] 203 ++ lib.optionals config.services.gnome.gnome-initial-setup.enable [ 204 # Create stamp file for gnome-initial-setup to prevent it starting in GDM. 205 "f /run/gdm/.config/gnome-initial-setup-done 0711 gdm gdm - yes" 206 ]; 207 208 # Otherwise GDM will not be able to start correctly and display Wayland sessions 209 systemd.packages = [ 210 gdm 211 pkgs.gnome-session 212 pkgs.gnome-shell 213 ]; 214 environment.systemPackages = [ 215 pkgs.adwaita-icon-theme 216 pkgs.gdm # For polkit rules 217 ]; 218 219 # We dont use the upstream gdm service 220 # it has to be disabled since the gdm package has it 221 # https://github.com/NixOS/nixpkgs/issues/108672 222 systemd.services.gdm.enable = false; 223 224 systemd.services.display-manager.wants = [ 225 # Because sd_login_monitor_new requires /run/systemd/machines 226 "systemd-machined.service" 227 # setSessionScript wants AccountsService 228 "accounts-daemon.service" 229 ]; 230 231 systemd.services.display-manager.after = [ 232 "rc-local.service" 233 "systemd-machined.service" 234 "systemd-user-sessions.service" 235 "getty@tty${gdm.initialVT}.service" 236 "plymouth-quit.service" 237 "plymouth-start.service" 238 ]; 239 systemd.services.display-manager.conflicts = [ 240 "getty@tty${gdm.initialVT}.service" 241 "plymouth-quit.service" 242 ]; 243 systemd.services.display-manager.onFailure = [ 244 "plymouth-quit.service" 245 ]; 246 247 # Prevent nixos-rebuild switch from bringing down the graphical 248 # session. (If multi-user.target wants plymouth-quit.service which 249 # conflicts display-manager.service, then when nixos-rebuild 250 # switch starts multi-user.target, display-manager.service is 251 # stopped so plymouth-quit.service can be started.) 252 systemd.services.plymouth-quit = lib.mkIf config.boot.plymouth.enable { 253 wantedBy = lib.mkForce [ ]; 254 }; 255 256 systemd.services.display-manager.serviceConfig = { 257 # Restart = "always"; - already defined in xserver.nix 258 KillMode = "mixed"; 259 IgnoreSIGPIPE = "no"; 260 BusName = "org.gnome.DisplayManager"; 261 StandardError = "inherit"; 262 ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID"; 263 KeyringMode = "shared"; 264 EnvironmentFile = "-/etc/locale.conf"; 265 }; 266 267 systemd.services.display-manager.path = [ pkgs.gnome-session ]; 268 269 # Allow choosing an user account 270 services.accounts-daemon.enable = true; 271 272 services.dbus.packages = [ gdm ]; 273 274 systemd.user.services.dbus.wantedBy = [ "default.target" ]; 275 276 programs.dconf.profiles.gdm.databases = 277 lib.optionals (!cfg.gdm.autoSuspend) [ 278 { 279 settings."org/gnome/settings-daemon/plugins/power" = { 280 sleep-inactive-ac-type = "nothing"; 281 sleep-inactive-battery-type = "nothing"; 282 sleep-inactive-ac-timeout = lib.gvariant.mkInt32 0; 283 sleep-inactive-battery-timeout = lib.gvariant.mkInt32 0; 284 }; 285 } 286 ] 287 ++ lib.optionals (cfg.gdm.banner != null) [ 288 { 289 settings."org/gnome/login-screen" = { 290 banner-message-enable = true; 291 banner-message-text = cfg.gdm.banner; 292 }; 293 } 294 ] 295 ++ [ "${gdm}/share/gdm/greeter-dconf-defaults" ]; 296 297 # Use AutomaticLogin if delay is zero, because it's immediate. 298 # Otherwise with TimedLogin with zero seconds the prompt is still 299 # presented and there's a little delay. 300 services.xserver.displayManager.gdm.settings = { 301 daemon = lib.mkMerge [ 302 { WaylandEnable = cfg.gdm.wayland; } 303 # nested if else didn't work 304 (lib.mkIf (config.services.displayManager.autoLogin.enable && cfg.gdm.autoLogin.delay != 0) { 305 TimedLoginEnable = true; 306 TimedLogin = config.services.displayManager.autoLogin.user; 307 TimedLoginDelay = cfg.gdm.autoLogin.delay; 308 }) 309 (lib.mkIf (config.services.displayManager.autoLogin.enable && cfg.gdm.autoLogin.delay == 0) { 310 AutomaticLoginEnable = true; 311 AutomaticLogin = config.services.displayManager.autoLogin.user; 312 }) 313 ]; 314 debug = lib.mkIf cfg.gdm.debug { 315 Enable = true; 316 }; 317 }; 318 319 environment.etc."gdm/custom.conf".source = configFile; 320 321 environment.etc."gdm/Xsession".source = config.services.displayManager.sessionData.wrapper; 322 323 # GDM LFS PAM modules, adapted somehow to NixOS 324 security.pam.services = { 325 gdm-launch-environment.text = '' 326 auth required pam_succeed_if.so audit quiet_success user = gdm 327 auth optional pam_permit.so 328 329 account required pam_succeed_if.so audit quiet_success user = gdm 330 account sufficient pam_unix.so 331 332 password required pam_deny.so 333 334 session required pam_succeed_if.so audit quiet_success user = gdm 335 session required pam_env.so conffile=/etc/pam/environment readenv=0 336 session optional ${config.systemd.package}/lib/security/pam_systemd.so 337 session optional pam_keyinit.so force revoke 338 session optional pam_permit.so 339 ''; 340 341 gdm-password.text = '' 342 auth substack login 343 account include login 344 password substack login 345 session include login 346 ''; 347 348 gdm-autologin.text = '' 349 auth requisite pam_nologin.so 350 auth required pam_succeed_if.so uid >= 1000 quiet 351 ${lib.optionalString (pamLogin.enable && pamLogin.enableGnomeKeyring) '' 352 auth [success=ok default=1] ${gdm}/lib/security/pam_gdm.so 353 auth optional ${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so 354 ''} 355 auth required pam_permit.so 356 357 account sufficient pam_unix.so 358 359 password requisite pam_unix.so nullok yescrypt 360 361 session optional pam_keyinit.so revoke 362 session include login 363 ''; 364 365 # This would block password prompt when included by gdm-password. 366 # GDM will instead run gdm-fingerprint in parallel. 367 login.fprintAuth = lib.mkIf config.services.fprintd.enable false; 368 369 gdm-fingerprint.text = lib.mkIf config.services.fprintd.enable '' 370 auth required pam_shells.so 371 auth requisite pam_nologin.so 372 auth requisite pam_faillock.so preauth 373 auth required ${pkgs.fprintd}/lib/security/pam_fprintd.so 374 auth required pam_env.so 375 ${lib.optionalString (pamLogin.enable && pamLogin.enableGnomeKeyring) '' 376 auth [success=ok default=1] ${gdm}/lib/security/pam_gdm.so 377 auth optional ${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so 378 ''} 379 380 account include login 381 382 password required pam_deny.so 383 384 session include login 385 ''; 386 }; 387 388 }; 389 390}