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