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