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