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