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.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 "displayManager"
45 "autoLogin"
46 "enable"
47 ])
48 (mkRenamedOptionModule [ "services" "xserver" "displayManager" "gdm" "autoLogin" "user" ] [
49 "services"
50 "displayManager"
51 "autoLogin"
52 "user"
53 ])
54
55 (mkRemovedOptionModule [ "services" "xserver" "displayManager" "gdm" "nvidiaWayland" ] "We defer to GDM whether Wayland should be enabled.")
56 ];
57
58 meta = {
59 maintainers = teams.gnome.members;
60 };
61
62 ###### interface
63
64 options = {
65
66 services.xserver.displayManager.gdm = {
67
68 enable = mkEnableOption "GDM, the GNOME Display Manager";
69
70 debug = mkEnableOption "debugging messages in GDM";
71
72 # Auto login options specific to GDM
73 autoLogin.delay = mkOption {
74 type = types.int;
75 default = 0;
76 description = ''
77 Seconds of inactivity after which the autologin will be performed.
78 '';
79 };
80
81 wayland = mkOption {
82 type = types.bool;
83 default = true;
84 description = ''
85 Allow GDM to run on Wayland instead of Xserver.
86 '';
87 };
88
89 autoSuspend = mkOption {
90 default = true;
91 description = ''
92 On the GNOME Display Manager login screen, suspend the machine after inactivity.
93 (Does not affect automatic suspend while logged in, or at lock screen.)
94 '';
95 type = types.bool;
96 };
97
98 banner = mkOption {
99 type = types.nullOr types.lines;
100 default = null;
101 example = ''
102 foo
103 bar
104 baz
105 '';
106 description = ''
107 Optional message to display on the login screen.
108 '';
109 };
110
111 settings = mkOption {
112 type = settingsFormat.type;
113 default = { };
114 example = {
115 debug.enable = true;
116 };
117 description = ''
118 Options passed to the gdm daemon.
119 See [here](https://help.gnome.org/admin/gdm/stable/configuration.html.en#daemonconfig) for supported options.
120 '';
121 };
122
123 };
124
125 };
126
127
128 ###### implementation
129
130 config = mkIf cfg.gdm.enable {
131
132 services.xserver.displayManager.lightdm.enable = false;
133
134 users.users.gdm =
135 { name = "gdm";
136 uid = config.ids.uids.gdm;
137 group = "gdm";
138 home = "/run/gdm";
139 description = "GDM user";
140 };
141
142 users.groups.gdm.gid = config.ids.gids.gdm;
143
144 # GDM needs different xserverArgs, presumable because using wayland by default.
145 services.xserver.tty = null;
146 services.xserver.display = null;
147 services.xserver.verbose = null;
148
149 services.displayManager =
150 {
151 environment = {
152 GDM_X_SERVER_EXTRA_ARGS = toString
153 (filter (arg: arg != "-terminate") cfg.xserverArgs);
154 XDG_DATA_DIRS = lib.makeSearchPath "share" [
155 gdm # for gnome-login.session
156 config.services.displayManager.sessionData.desktops
157 pkgs.gnome.gnome-control-center # for accessibility icon
158 pkgs.gnome.adwaita-icon-theme
159 pkgs.hicolor-icon-theme # empty icon theme as a base
160 ];
161 } // optionalAttrs (xSessionWrapper != null) {
162 # Make GDM use this wrapper before running the session, which runs the
163 # configured setupCommands. This relies on a patched GDM which supports
164 # this environment variable.
165 GDM_X_SESSION_WRAPPER = "${xSessionWrapper}";
166 };
167 execCmd = "exec ${gdm}/bin/gdm";
168 preStart = optionalString (defaultSessionName != null) ''
169 # Set default session in session chooser to a specified values – basically ignore session history.
170 ${setSessionScript}/bin/set-session ${config.services.displayManager.sessionData.autologinSession}
171 '';
172 };
173
174 systemd.tmpfiles.rules = [
175 "d /run/gdm/.config 0711 gdm gdm"
176 ] ++ optionals config.hardware.pulseaudio.enable [
177 "d /run/gdm/.config/pulse 0711 gdm gdm"
178 "L+ /run/gdm/.config/pulse/${pulseConfig.name} - - - - ${pulseConfig}"
179 ] ++ optionals config.services.gnome.gnome-initial-setup.enable [
180 # Create stamp file for gnome-initial-setup to prevent it starting in GDM.
181 "f /run/gdm/.config/gnome-initial-setup-done 0711 gdm gdm - yes"
182 ];
183
184 # Otherwise GDM will not be able to start correctly and display Wayland sessions
185 systemd.packages = with pkgs.gnome; [ gdm gnome-session gnome-shell ];
186 environment.systemPackages = [ pkgs.gnome.adwaita-icon-theme ];
187
188 # We dont use the upstream gdm service
189 # it has to be disabled since the gdm package has it
190 # https://github.com/NixOS/nixpkgs/issues/108672
191 systemd.services.gdm.enable = false;
192
193 systemd.services.display-manager.wants = [
194 # Because sd_login_monitor_new requires /run/systemd/machines
195 "systemd-machined.service"
196 # setSessionScript wants AccountsService
197 "accounts-daemon.service"
198 ];
199
200 systemd.services.display-manager.after = [
201 "rc-local.service"
202 "systemd-machined.service"
203 "systemd-user-sessions.service"
204 "getty@tty${gdm.initialVT}.service"
205 "plymouth-quit.service"
206 "plymouth-start.service"
207 ];
208 systemd.services.display-manager.conflicts = [
209 "getty@tty${gdm.initialVT}.service"
210 "plymouth-quit.service"
211 ];
212 systemd.services.display-manager.onFailure = [
213 "plymouth-quit.service"
214 ];
215
216 # Prevent nixos-rebuild switch from bringing down the graphical
217 # session. (If multi-user.target wants plymouth-quit.service which
218 # conflicts display-manager.service, then when nixos-rebuild
219 # switch starts multi-user.target, display-manager.service is
220 # stopped so plymouth-quit.service can be started.)
221 systemd.services.plymouth-quit = mkIf config.boot.plymouth.enable {
222 wantedBy = lib.mkForce [];
223 };
224
225 systemd.services.display-manager.serviceConfig = {
226 # Restart = "always"; - already defined in xserver.nix
227 KillMode = "mixed";
228 IgnoreSIGPIPE = "no";
229 BusName = "org.gnome.DisplayManager";
230 StandardError = "inherit";
231 ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
232 KeyringMode = "shared";
233 EnvironmentFile = "-/etc/locale.conf";
234 };
235
236 systemd.services.display-manager.path = [ pkgs.gnome.gnome-session ];
237
238 # Allow choosing an user account
239 services.accounts-daemon.enable = true;
240
241 services.dbus.packages = [ gdm ];
242
243 systemd.user.services.dbus.wantedBy = [ "default.target" ];
244
245 programs.dconf.profiles.gdm.databases = lib.optionals (!cfg.gdm.autoSuspend) [{
246 settings."org/gnome/settings-daemon/plugins/power" = {
247 sleep-inactive-ac-type = "nothing";
248 sleep-inactive-battery-type = "nothing";
249 sleep-inactive-ac-timeout = lib.gvariant.mkInt32 0;
250 sleep-inactive-battery-timeout = lib.gvariant.mkInt32 0;
251 };
252 }] ++ lib.optionals (cfg.gdm.banner != null) [{
253 settings."org/gnome/login-screen" = {
254 banner-message-enable = true;
255 banner-message-text = cfg.gdm.banner;
256 };
257 }] ++ [ "${gdm}/share/gdm/greeter-dconf-defaults" ];
258
259 # Use AutomaticLogin if delay is zero, because it's immediate.
260 # Otherwise with TimedLogin with zero seconds the prompt is still
261 # presented and there's a little delay.
262 services.xserver.displayManager.gdm.settings = {
263 daemon = mkMerge [
264 { WaylandEnable = cfg.gdm.wayland; }
265 # nested if else didn't work
266 (mkIf (config.services.displayManager.autoLogin.enable && cfg.gdm.autoLogin.delay != 0 ) {
267 TimedLoginEnable = true;
268 TimedLogin = config.services.displayManager.autoLogin.user;
269 TimedLoginDelay = cfg.gdm.autoLogin.delay;
270 })
271 (mkIf (config.services.displayManager.autoLogin.enable && cfg.gdm.autoLogin.delay == 0 ) {
272 AutomaticLoginEnable = true;
273 AutomaticLogin = config.services.displayManager.autoLogin.user;
274 })
275 ];
276 debug = mkIf cfg.gdm.debug {
277 Enable = true;
278 };
279 };
280
281 environment.etc."gdm/custom.conf".source = configFile;
282
283 environment.etc."gdm/Xsession".source = config.services.displayManager.sessionData.wrapper;
284
285 # GDM LFS PAM modules, adapted somehow to NixOS
286 security.pam.services = {
287 gdm-launch-environment.text = ''
288 auth required pam_succeed_if.so audit quiet_success user = gdm
289 auth optional pam_permit.so
290
291 account required pam_succeed_if.so audit quiet_success user = gdm
292 account sufficient pam_unix.so
293
294 password required pam_deny.so
295
296 session required pam_succeed_if.so audit quiet_success user = gdm
297 session required pam_env.so conffile=/etc/pam/environment readenv=0
298 session optional ${config.systemd.package}/lib/security/pam_systemd.so
299 session optional pam_keyinit.so force revoke
300 session optional pam_permit.so
301 '';
302
303 gdm-password.text = ''
304 auth substack login
305 account include login
306 password substack login
307 session include login
308 '';
309
310 gdm-autologin.text = ''
311 auth requisite pam_nologin.so
312
313 auth required pam_succeed_if.so uid >= 1000 quiet
314 auth required pam_permit.so
315
316 account sufficient pam_unix.so
317
318 password requisite pam_unix.so nullok yescrypt
319
320 session optional pam_keyinit.so revoke
321 session include login
322 '';
323
324 };
325
326 };
327
328}