1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8with lib;
9
10let
11
12 xcfg = config.services.xserver;
13 dmcfg = config.services.displayManager;
14 xEnv = config.systemd.services.display-manager.environment;
15 cfg = xcfg.displayManager.lightdm;
16 sessionData = dmcfg.sessionData;
17
18 setSessionScript = pkgs.callPackage ./account-service-util.nix { };
19
20 inherit (pkgs) lightdm writeScript writeText;
21
22 # lightdm runs with clearenv(), but we need a few things in the environment for X to startup
23 xserverWrapper = writeScript "xserver-wrapper" ''
24 #! ${pkgs.bash}/bin/bash
25 ${concatMapStrings (n: "export ${n}=\"${getAttr n xEnv}\"\n") (attrNames xEnv)}
26
27 display=$(echo "$@" | xargs -n 1 | grep -P ^:\\d\$ | head -n 1 | sed s/^://)
28 if [ -z "$display" ]
29 then additionalArgs=":0 -logfile /var/log/X.0.log"
30 else additionalArgs="-logfile /var/log/X.$display.log"
31 fi
32
33 exec ${xcfg.displayManager.xserverBin} ${toString xcfg.displayManager.xserverArgs} $additionalArgs "$@"
34 '';
35
36 usersConf = writeText "users.conf" ''
37 [UserList]
38 minimum-uid=1000
39 hidden-users=${concatStringsSep " " dmcfg.hiddenUsers}
40 hidden-shells=/run/current-system/sw/bin/nologin
41 '';
42
43 lightdmConf = writeText "lightdm.conf" ''
44 [LightDM]
45 ${optionalString cfg.greeter.enable ''
46 greeter-user = ${config.users.users.lightdm.name}
47 greeters-directory = ${cfg.greeter.package}
48 ''}
49 sessions-directory = ${dmcfg.sessionData.desktops}/share/xsessions:${dmcfg.sessionData.desktops}/share/wayland-sessions
50 ${cfg.extraConfig}
51
52 [Seat:*]
53 xserver-command = ${xserverWrapper}
54 session-wrapper = ${dmcfg.sessionData.wrapper}
55 ${optionalString cfg.greeter.enable ''
56 greeter-session = ${cfg.greeter.name}
57 ''}
58 ${optionalString dmcfg.autoLogin.enable ''
59 autologin-user = ${dmcfg.autoLogin.user}
60 autologin-user-timeout = ${toString cfg.autoLogin.timeout}
61 autologin-session = ${sessionData.autologinSession}
62 ''}
63 ${optionalString (xcfg.displayManager.setupCommands != "") ''
64 display-setup-script=${pkgs.writeScript "lightdm-display-setup" ''
65 #!${pkgs.bash}/bin/bash
66 ${xcfg.displayManager.setupCommands}
67 ''}
68 ''}
69 ${cfg.extraSeatDefaults}
70 '';
71
72in
73{
74 meta = with lib; {
75 maintainers = with maintainers; [ ] ++ teams.pantheon.members;
76 };
77
78 # Note: the order in which lightdm greeter modules are imported
79 # here determines the default: later modules (if enable) are
80 # preferred.
81 imports = [
82 ./lightdm-greeters/gtk.nix
83 ./lightdm-greeters/mini.nix
84 ./lightdm-greeters/enso-os.nix
85 ./lightdm-greeters/pantheon.nix
86 ./lightdm-greeters/lomiri.nix
87 ./lightdm-greeters/tiny.nix
88 ./lightdm-greeters/slick.nix
89 ./lightdm-greeters/mobile.nix
90 (mkRenamedOptionModule
91 [ "services" "xserver" "displayManager" "lightdm" "autoLogin" "enable" ]
92 [
93 "services"
94 "displayManager"
95 "autoLogin"
96 "enable"
97 ]
98 )
99 (mkRenamedOptionModule
100 [ "services" "xserver" "displayManager" "lightdm" "autoLogin" "user" ]
101 [
102 "services"
103 "displayManager"
104 "autoLogin"
105 "user"
106 ]
107 )
108 ];
109
110 options = {
111
112 services.xserver.displayManager.lightdm = {
113
114 enable = mkOption {
115 type = types.bool;
116 default = false;
117 description = ''
118 Whether to enable lightdm as the display manager.
119 '';
120 };
121
122 greeter = {
123 enable = mkOption {
124 type = types.bool;
125 default = true;
126 description = ''
127 If set to false, run lightdm in greeterless mode. This only works if autologin
128 is enabled and autoLogin.timeout is zero.
129 '';
130 };
131 package = mkOption {
132 type = types.package;
133 description = ''
134 The LightDM greeter to login via. The package should be a directory
135 containing a .desktop file matching the name in the 'name' option.
136 '';
137
138 };
139 name = mkOption {
140 type = types.str;
141 description = ''
142 The name of a .desktop file in the directory specified
143 in the 'package' option.
144 '';
145 };
146 };
147
148 extraConfig = mkOption {
149 type = types.lines;
150 default = "";
151 example = ''
152 user-authority-in-system-dir = true
153 '';
154 description = "Extra lines to append to LightDM section.";
155 };
156
157 background = mkOption {
158 type = types.either types.path (types.strMatching "^#[0-9]{6}$");
159 # Manual cannot depend on packages, we are actually setting the default in config below.
160 defaultText = literalExpression "pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom.gnomeFilePath";
161 description = ''
162 The background image or color to use.
163 '';
164 };
165
166 extraSeatDefaults = mkOption {
167 type = types.lines;
168 default = "";
169 example = ''
170 greeter-show-manual-login=true
171 '';
172 description = "Extra lines to append to SeatDefaults section.";
173 };
174
175 # Configuration for automatic login specific to LightDM
176 autoLogin.timeout = mkOption {
177 type = types.int;
178 default = 0;
179 description = ''
180 Show the greeter for this many seconds before automatic login occurs.
181 '';
182 };
183
184 };
185 };
186
187 config = mkIf cfg.enable {
188
189 assertions = [
190 {
191 assertion = xcfg.enable;
192 message = ''
193 LightDM requires services.xserver.enable to be true
194 '';
195 }
196 {
197 assertion = dmcfg.autoLogin.enable -> sessionData.autologinSession != null;
198 message = ''
199 LightDM auto-login requires that services.displayManager.defaultSession is set.
200 '';
201 }
202 {
203 assertion = !cfg.greeter.enable -> (dmcfg.autoLogin.enable && cfg.autoLogin.timeout == 0);
204 message = ''
205 LightDM can only run without greeter if automatic login is enabled and the timeout for it
206 is set to zero.
207 '';
208 }
209 ];
210
211 # Keep in sync with the defaultText value from the option definition.
212 services.xserver.displayManager.lightdm.background =
213 mkDefault pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom.gnomeFilePath;
214
215 # Set default session in session chooser to a specified values – basically ignore session history.
216 # Auto-login is already covered by a config value.
217 services.displayManager.preStart =
218 optionalString (!dmcfg.autoLogin.enable && dmcfg.defaultSession != null)
219 ''
220 ${setSessionScript}/bin/set-session ${dmcfg.defaultSession}
221 '';
222
223 # setSessionScript needs session-files in XDG_DATA_DIRS
224 services.displayManager.environment.XDG_DATA_DIRS = "${dmcfg.sessionData.desktops}/share/";
225
226 # setSessionScript wants AccountsService
227 systemd.services.display-manager.wants = [
228 "accounts-daemon.service"
229 ];
230
231 # lightdm relaunches itself via just `lightdm`, so needs to be on the PATH
232 services.displayManager.execCmd = ''
233 export PATH=${lightdm}/sbin:$PATH
234 exec ${lightdm}/sbin/lightdm
235 '';
236
237 # Replaces getty
238 systemd.services.display-manager.conflicts = [
239 "getty@tty7.service"
240 # TODO: Add "plymouth-quit.service" so LightDM can control when plymouth
241 # quits. Currently this breaks switching to configurations with plymouth.
242 ];
243
244 # Pull in dependencies of services we replace.
245 systemd.services.display-manager.after = [
246 "rc-local.service"
247 "systemd-machined.service"
248 "systemd-user-sessions.service"
249 "getty@tty7.service"
250 "user.slice"
251 ];
252
253 # user.slice needs to be present
254 systemd.services.display-manager.requires = [
255 "user.slice"
256 ];
257
258 # lightdm stops plymouth so when it fails make sure plymouth stops.
259 systemd.services.display-manager.onFailure = [
260 "plymouth-quit.service"
261 ];
262
263 systemd.services.display-manager.serviceConfig = {
264 BusName = "org.freedesktop.DisplayManager";
265 IgnoreSIGPIPE = "no";
266 # This allows lightdm to pass the LUKS password through to PAM.
267 # login keyring is unlocked automatic when autologin is used.
268 KeyringMode = "shared";
269 KillMode = "mixed";
270 StandardError = "inherit";
271 };
272
273 environment.etc."lightdm/lightdm.conf".source = lightdmConf;
274 environment.etc."lightdm/users.conf".source = usersConf;
275
276 services.dbus.enable = true;
277 services.dbus.packages = [ lightdm ];
278
279 # lightdm uses the accounts daemon to remember language/window-manager per user
280 services.accounts-daemon.enable = true;
281
282 # Enable the accounts daemon to find lightdm's dbus interface
283 environment.systemPackages = [ lightdm ];
284
285 security.polkit.enable = true;
286
287 security.pam.services.lightdm.text = ''
288 auth substack login
289 account include login
290 password substack login
291 session include login
292 '';
293
294 security.pam.services.lightdm-greeter.text = ''
295 auth required pam_succeed_if.so audit quiet_success user = lightdm
296 auth optional pam_permit.so
297
298 account required pam_succeed_if.so audit quiet_success user = lightdm
299 account sufficient pam_unix.so
300
301 password required pam_deny.so
302
303 session required pam_succeed_if.so audit quiet_success user = lightdm
304 session required pam_env.so conffile=/etc/pam/environment readenv=0
305 session optional ${config.systemd.package}/lib/security/pam_systemd.so
306 session optional pam_keyinit.so force revoke
307 session optional pam_permit.so
308 '';
309
310 security.pam.services.lightdm-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 users.users.lightdm = {
325 home = "/var/lib/lightdm";
326 group = "lightdm";
327 uid = config.ids.uids.lightdm;
328 };
329
330 systemd.tmpfiles.rules = [
331 "d /run/lightdm 0711 lightdm lightdm -"
332 "d /var/cache/lightdm 0711 root lightdm -"
333 "d /var/lib/lightdm 1770 lightdm lightdm -"
334 "d /var/lib/lightdm-data 1775 lightdm lightdm -"
335 "d /var/log/lightdm 0711 root lightdm -"
336 ];
337
338 users.groups.lightdm.gid = config.ids.gids.lightdm;
339 services.xserver.tty = null; # We might start multiple X servers so let the tty increment themselves..
340 services.xserver.display = null; # We specify our own display (and logfile) in xserver-wrapper up there
341 };
342}