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