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