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}