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}