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}