1 { config, lib, pkgs, ... }:
5 cfg = config.services.xserver.displayManager;
7 pamCfg = config.security.pam.services;
8 settingsFormat = pkgs.formats.ini { };
9 configFile = settingsFormat.generate "custom.conf" cfg.gdm.settings;
11 xSessionWrapper = if (cfg.setupCommands == "") then null else
12 pkgs.writeScript "gdm-x-session-wrapper" ''
13 #!${pkgs.bash}/bin/bash
18 # Solves problems like:
19 # https://wiki.archlinux.org/index.php/Talk:Bluetooth_headset#GDMs_pulseaudio_instance_captures_bluetooth_headset
20 # Instead of blacklisting plugins, we use Fedora's PulseAudio configuration for GDM:
21 # https://src.fedoraproject.org/rpms/gdm/blob/master/f/default.pa-for-gdm
22 pulseConfig = pkgs.writeText "default.pa" ''
23 load-module module-device-restore
24 load-module module-card-restore
25 load-module module-udev-detect
26 load-module module-native-protocol-unix
27 load-module module-default-device-restore
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
34 defaultSessionName = config.services.displayManager.defaultSession;
36 setSessionScript = pkgs.callPackage ./account-service-util.nix { };
41 (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "gdm" "autoLogin" "enable" ] [
47 (lib.mkRenamedOptionModule [ "services" "xserver" "displayManager" "gdm" "autoLogin" "user" ] [
54 (lib.mkRemovedOptionModule [ "services" "xserver" "displayManager" "gdm" "nvidiaWayland" ] "We defer to GDM whether Wayland should be enabled.")
58 maintainers = lib.teams.gnome.members;
65 services.xserver.displayManager.gdm = {
67 enable = lib.mkEnableOption "GDM, the GNOME Display Manager";
69 debug = lib.mkEnableOption "debugging messages in GDM";
71 # Auto login options specific to GDM
72 autoLogin.delay = lib.mkOption {
76 Seconds of inactivity after which the autologin will be performed.
80 wayland = lib.mkOption {
81 type = lib.types.bool;
84 Allow GDM to run on Wayland instead of Xserver.
88 autoSuspend = lib.mkOption {
91 On the GNOME Display Manager login screen, suspend the machine after inactivity.
92 (Does not affect automatic suspend while logged in, or at lock screen.)
94 type = lib.types.bool;
97 banner = lib.mkOption {
98 type = lib.types.nullOr lib.types.lines;
106 Optional message to display on the login screen.
110 settings = lib.mkOption {
111 type = settingsFormat.type;
117 Options passed to the gdm daemon.
118 See [here](https://help.gnome.org/admin/gdm/stable/configuration.html.en#daemonconfig) for supported options.
127 ###### implementation
129 config = lib.mkIf cfg.gdm.enable {
131 services.xserver.displayManager.lightdm.enable = false;
135 uid = config.ids.uids.gdm;
138 description = "GDM user";
141 users.groups.gdm.gid = config.ids.gids.gdm;
143 # GDM needs different xserverArgs, presumable because using wayland by default.
144 services.xserver.tty = null;
145 services.xserver.display = null;
146 services.xserver.verbose = null;
148 services.displayManager =
151 GDM_X_SERVER_EXTRA_ARGS = toString
152 (lib.filter (arg: arg != "-terminate") cfg.xserverArgs);
153 XDG_DATA_DIRS = lib.makeSearchPath "share" [
154 gdm # for gnome-login.session
155 config.services.displayManager.sessionData.desktops
156 pkgs.gnome-control-center # for accessibility icon
157 pkgs.adwaita-icon-theme
158 pkgs.hicolor-icon-theme # empty icon theme as a base
160 } // lib.optionalAttrs (xSessionWrapper != null) {
161 # Make GDM use this wrapper before running the session, which runs the
162 # configured setupCommands. This relies on a patched GDM which supports
163 # this environment variable.
164 GDM_X_SESSION_WRAPPER = "${xSessionWrapper}";
166 execCmd = "exec ${gdm}/bin/gdm";
167 preStart = lib.optionalString (defaultSessionName != null) ''
168 # Set default session in session chooser to a specified values – basically ignore session history.
169 ${setSessionScript}/bin/set-session ${config.services.displayManager.sessionData.autologinSession}
173 systemd.tmpfiles.rules = [
174 "d /run/gdm/.config 0711 gdm gdm"
175 ] ++ lib.optionals config.hardware.pulseaudio.enable [
176 "d /run/gdm/.config/pulse 0711 gdm gdm"
177 "L+ /run/gdm/.config/pulse/${pulseConfig.name} - - - - ${pulseConfig}"
178 ] ++ lib.optionals config.services.gnome.gnome-initial-setup.enable [
179 # Create stamp file for gnome-initial-setup to prevent it starting in GDM.
180 "f /run/gdm/.config/gnome-initial-setup-done 0711 gdm gdm - yes"
183 # Otherwise GDM will not be able to start correctly and display Wayland sessions
184 systemd.packages = with pkgs.gnome; [
189 environment.systemPackages = [ pkgs.adwaita-icon-theme ];
191 # We dont use the upstream gdm service
192 # it has to be disabled since the gdm package has it
193 # https://github.com/NixOS/nixpkgs/issues/108672
194 systemd.services.gdm.enable = false;
196 systemd.services.display-manager.wants = [
197 # Because sd_login_monitor_new requires /run/systemd/machines
198 "systemd-machined.service"
199 # setSessionScript wants AccountsService
200 "accounts-daemon.service"
203 systemd.services.display-manager.after = [
205 "systemd-machined.service"
206 "systemd-user-sessions.service"
207 "getty@tty${gdm.initialVT}.service"
208 "plymouth-quit.service"
209 "plymouth-start.service"
211 systemd.services.display-manager.conflicts = [
212 "getty@tty${gdm.initialVT}.service"
213 "plymouth-quit.service"
215 systemd.services.display-manager.onFailure = [
216 "plymouth-quit.service"
219 # Prevent nixos-rebuild switch from bringing down the graphical
220 # session. (If multi-user.target wants plymouth-quit.service which
221 # conflicts display-manager.service, then when nixos-rebuild
222 # switch starts multi-user.target, display-manager.service is
223 # stopped so plymouth-quit.service can be started.)
224 systemd.services.plymouth-quit = lib.mkIf config.boot.plymouth.enable {
225 wantedBy = lib.mkForce [];
228 systemd.services.display-manager.serviceConfig = {
229 # Restart = "always"; - already defined in xserver.nix
231 IgnoreSIGPIPE = "no";
232 BusName = "org.gnome.DisplayManager";
233 StandardError = "inherit";
234 ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
235 KeyringMode = "shared";
236 EnvironmentFile = "-/etc/locale.conf";
239 systemd.services.display-manager.path = [ pkgs.gnome-session ];
241 # Allow choosing an user account
242 services.accounts-daemon.enable = true;
244 services.dbus.packages = [ gdm ];
246 systemd.user.services.dbus.wantedBy = [ "default.target" ];
248 programs.dconf.profiles.gdm.databases = lib.optionals (!cfg.gdm.autoSuspend) [{
249 settings."org/gnome/settings-daemon/plugins/power" = {
250 sleep-inactive-ac-type = "nothing";
251 sleep-inactive-battery-type = "nothing";
252 sleep-inactive-ac-timeout = lib.gvariant.mkInt32 0;
253 sleep-inactive-battery-timeout = lib.gvariant.mkInt32 0;
255 }] ++ lib.optionals (cfg.gdm.banner != null) [{
256 settings."org/gnome/login-screen" = {
257 banner-message-enable = true;
258 banner-message-text = cfg.gdm.banner;
260 }] ++ [ "${gdm}/share/gdm/greeter-dconf-defaults" ];
262 # Use AutomaticLogin if delay is zero, because it's immediate.
263 # Otherwise with TimedLogin with zero seconds the prompt is still
264 # presented and there's a little delay.
265 services.xserver.displayManager.gdm.settings = {
266 daemon = lib.mkMerge [
267 { WaylandEnable = cfg.gdm.wayland; }
268 # nested if else didn't work
269 (lib.mkIf (config.services.displayManager.autoLogin.enable && cfg.gdm.autoLogin.delay != 0 ) {
270 TimedLoginEnable = true;
271 TimedLogin = config.services.displayManager.autoLogin.user;
272 TimedLoginDelay = cfg.gdm.autoLogin.delay;
274 (lib.mkIf (config.services.displayManager.autoLogin.enable && cfg.gdm.autoLogin.delay == 0 ) {
275 AutomaticLoginEnable = true;
276 AutomaticLogin = config.services.displayManager.autoLogin.user;
279 debug = lib.mkIf cfg.gdm.debug {
284 environment.etc."gdm/custom.conf".source = configFile;
286 environment.etc."gdm/Xsession".source = config.services.displayManager.sessionData.wrapper;
288 # GDM LFS PAM modules, adapted somehow to NixOS
289 security.pam.services = {
290 gdm-launch-environment.text = ''
291 auth required pam_succeed_if.so audit quiet_success user = gdm
292 auth optional pam_permit.so
294 account required pam_succeed_if.so audit quiet_success user = gdm
295 account sufficient pam_unix.so
297 password required pam_deny.so
299 session required pam_succeed_if.so audit quiet_success user = gdm
300 session required pam_env.so conffile=/etc/pam/environment readenv=0
301 session optional ${config.systemd.package}/lib/security/pam_systemd.so
302 session optional pam_keyinit.so force revoke
303 session optional pam_permit.so
306 gdm-password.text = ''
308 account include login
309 password substack login
310 session include login
313 gdm-autologin.text = ''
314 auth requisite pam_nologin.so
315 auth required pam_succeed_if.so uid >= 1000 quiet
316 ${lib.optionalString pamCfg.login.enableGnomeKeyring ''
317 auth [success=ok default=1] ${gdm}/lib/security/pam_gdm.so
318 auth optional ${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so
320 auth required pam_permit.so
322 account sufficient pam_unix.so
324 password requisite pam_unix.so nullok yescrypt
326 session optional pam_keyinit.so revoke
327 session include login
330 # This would block password prompt when included by gdm-password.
331 # GDM will instead run gdm-fingerprint in parallel.
332 login.fprintAuth = lib.mkIf config.services.fprintd.enable false;
334 gdm-fingerprint.text = lib.mkIf config.services.fprintd.enable ''
335 auth required pam_shells.so
336 auth requisite pam_nologin.so
337 auth requisite pam_faillock.so preauth
338 auth required ${pkgs.fprintd}/lib/security/pam_fprintd.so
339 auth required pam_env.so
340 ${lib.optionalString pamCfg.login.enableGnomeKeyring ''
341 auth [success=ok default=1] ${gdm}/lib/security/pam_gdm.so
342 auth optional ${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so
345 account include login
347 password required pam_deny.so
349 session include login