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