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