1{
2 config,
3 lib,
4 options,
5 pkgs,
6 ...
7}:
8
9with lib;
10
11let
12
13 plymouth = pkgs.plymouth.override {
14 systemd = config.boot.initrd.systemd.package;
15 };
16
17 cfg = config.boot.plymouth;
18 opt = options.boot.plymouth;
19
20 nixosBreezePlymouth = pkgs.kdePackages.breeze-plymouth.override {
21 logoFile = cfg.logo;
22 logoName = "nixos";
23 osName = config.system.nixos.distroName;
24 osVersion = config.system.nixos.release;
25 };
26
27 plymouthLogos = pkgs.runCommand "plymouth-logos" { inherit (cfg) logo; } ''
28 mkdir -p $out
29
30 # For themes that are compiled with PLYMOUTH_LOGO_FILE
31 mkdir -p $out/etc/plymouth
32 ln -s $logo $out/etc/plymouth/logo.png
33
34 # Logo for bgrt theme
35 # Note this is technically an abuse of watermark for the bgrt theme
36 # See: https://gitlab.freedesktop.org/plymouth/plymouth/-/issues/95#note_813768
37 mkdir -p $out/share/plymouth/themes/spinner
38 ln -s $logo $out/share/plymouth/themes/spinner/watermark.png
39
40 # Logo for spinfinity theme
41 # See: https://gitlab.freedesktop.org/plymouth/plymouth/-/issues/106
42 mkdir -p $out/share/plymouth/themes/spinfinity
43 ln -s $logo $out/share/plymouth/themes/spinfinity/header-image.png
44
45 # Logo for catppuccin (two-step) theme
46 for flavour in mocha macchiato latte frappe
47 do
48 mkdir -p $out/share/plymouth/themes/catppuccin-"$flavour"
49 ln -s $logo $out/share/plymouth/themes/catppuccin-"$flavour"/header-image.png
50 done
51 '';
52
53 themesEnv = pkgs.buildEnv {
54 name = "plymouth-themes";
55 paths = [
56 plymouth
57 plymouthLogos
58 ]
59 ++ cfg.themePackages;
60 };
61
62 configFile = pkgs.writeText "plymouthd.conf" ''
63 [Daemon]
64 ShowDelay=0
65 DeviceTimeout=8
66 Theme=${cfg.theme}
67 ${cfg.extraConfig}
68 '';
69
70 checkIfThemeExists = ''
71 # Check if the actual requested theme is here
72 if [[ ! -d ${themesEnv}/share/plymouth/themes/${cfg.theme} ]]; then
73 echo "The requested theme: ${cfg.theme} is not provided by any of the packages in boot.plymouth.themePackages"
74 echo "The following themes exist: $(ls ${themesEnv}/share/plymouth/themes/)"
75 exit 1
76 fi
77 '';
78
79 # 'emergency.serivce' and 'rescue.service' have
80 # 'ExecStartPre=-plymouth quit --wait', but 'plymouth' is not on
81 # their 'ExecSearchPath'. We could set 'ExecSearchPath', but it
82 # overrides 'DefaultEnvironment=PATH=...', which is trouble for the
83 # initrd shell. It's simpler to just reset 'ExecStartPre' with an
84 # empty string and then set it to exactly what we want.
85 preStartQuitFixup = {
86 serviceConfig.ExecStartPre = [
87 ""
88 "${plymouth}/bin/plymouth quit --wait"
89 ];
90 };
91
92in
93
94{
95
96 options = {
97
98 boot.plymouth = {
99
100 enable = mkEnableOption "Plymouth boot splash screen";
101
102 font = mkOption {
103 default = "${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf";
104 defaultText = literalExpression ''"''${pkgs.dejavu_fonts.minimal}/share/fonts/truetype/DejaVuSans.ttf"'';
105 type = types.path;
106 description = ''
107 Font file made available for displaying text on the splash screen.
108 '';
109 };
110
111 themePackages = mkOption {
112 default = lib.optional (cfg.theme == "breeze") nixosBreezePlymouth;
113 defaultText = literalMD ''
114 A NixOS branded variant of the breeze theme when
115 `config.${opt.theme} == "breeze"`, otherwise
116 `[ ]`.
117 '';
118 type = types.listOf types.package;
119 description = ''
120 Extra theme packages for plymouth.
121 '';
122 };
123
124 theme = mkOption {
125 default = "bgrt";
126 type = types.str;
127 description = ''
128 Splash screen theme.
129 '';
130 };
131
132 logo = mkOption {
133 type = types.path;
134 # Dimensions are 48x48 to match GDM logo
135 default = "${pkgs.nixos-icons}/share/icons/hicolor/48x48/apps/nix-snowflake-white.png";
136 defaultText = literalExpression ''"''${pkgs.nixos-icons}/share/icons/hicolor/48x48/apps/nix-snowflake-white.png"'';
137 example = literalExpression ''
138 pkgs.fetchurl {
139 url = "https://nixos.org/logo/nixos-hires.png";
140 sha256 = "1ivzgd7iz0i06y36p8m5w48fd8pjqwxhdaavc0pxs7w1g7mcy5si";
141 }
142 '';
143 description = ''
144 Logo which is displayed on the splash screen.
145 Currently supports PNG file format only.
146 '';
147 };
148
149 extraConfig = mkOption {
150 type = types.lines;
151 default = "";
152 description = ''
153 Literal string to append to `configFile`
154 and the config file generated by the plymouth module.
155 '';
156 };
157
158 };
159
160 };
161
162 config = mkIf cfg.enable {
163
164 boot.kernelParams = [ "splash" ];
165
166 # To be discoverable by systemd.
167 environment.systemPackages = [ plymouth ];
168
169 environment.etc."plymouth/plymouthd.conf".source = configFile;
170 environment.etc."plymouth/plymouthd.defaults".source =
171 "${plymouth}/share/plymouth/plymouthd.defaults";
172 environment.etc."plymouth/logo.png".source = cfg.logo;
173 environment.etc."plymouth/themes".source = "${themesEnv}/share/plymouth/themes";
174 # XXX: Needed because we supply a different set of plugins in initrd.
175 environment.etc."plymouth/plugins".source = "${plymouth}/lib/plymouth";
176
177 systemd.tmpfiles.rules = [
178 "d /run/plymouth 0755 root root 0 -"
179 "L+ /run/plymouth/plymouthd.defaults - - - - /etc/plymouth/plymouthd.defaults"
180 "L+ /run/plymouth/themes - - - - /etc/plymouth/themes"
181 "L+ /run/plymouth/plugins - - - - /etc/plymouth/plugins"
182 ];
183
184 systemd.packages = [ plymouth ];
185
186 systemd.services.plymouth-kexec.wantedBy = [ "kexec.target" ];
187 systemd.services.plymouth-halt.wantedBy = [ "halt.target" ];
188 systemd.services.plymouth-quit-wait.wantedBy = [ "multi-user.target" ];
189 systemd.services.plymouth-quit.wantedBy = [ "multi-user.target" ];
190 systemd.services.plymouth-poweroff.wantedBy = [ "poweroff.target" ];
191 systemd.services.plymouth-reboot.wantedBy = [ "reboot.target" ];
192 systemd.services.plymouth-read-write.wantedBy = [ "sysinit.target" ];
193 systemd.services.systemd-ask-password-plymouth.wantedBy = [ "sysinit.target" ];
194 systemd.paths.systemd-ask-password-plymouth.wantedBy = [ "sysinit.target" ];
195
196 # Prevent Plymouth taking over the screen during system updates.
197 systemd.services.plymouth-start.restartIfChanged = false;
198
199 systemd.services.rescue = preStartQuitFixup;
200 systemd.services.emergency = preStartQuitFixup;
201
202 boot.initrd.systemd = {
203 extraBin.plymouth = "${plymouth}/bin/plymouth"; # for the recovery shell
204 storePaths = [
205 "${lib.getBin config.boot.initrd.systemd.package}/bin/systemd-tty-ask-password-agent"
206 "${plymouth}/bin/plymouthd"
207 "${plymouth}/sbin/plymouthd"
208 ];
209 packages = [ plymouth ]; # systemd units
210
211 services.rescue = preStartQuitFixup;
212 services.emergency = preStartQuitFixup;
213
214 contents = {
215 # Files
216 "/etc/plymouth/plymouthd.conf".source = configFile;
217 "/etc/plymouth/logo.png".source = cfg.logo;
218 "/etc/plymouth/plymouthd.defaults".source = "${plymouth}/share/plymouth/plymouthd.defaults";
219 # Directories
220 "/etc/plymouth/plugins".source = pkgs.runCommand "plymouth-initrd-plugins" { } (
221 checkIfThemeExists
222 + ''
223 moduleName="$(sed -n 's,ModuleName *= *,,p' ${themesEnv}/share/plymouth/themes/${cfg.theme}/${cfg.theme}.plymouth)"
224
225 mkdir -p $out/renderers
226 # module might come from a theme
227 cp ${themesEnv}/lib/plymouth/*.so $out
228 cp ${plymouth}/lib/plymouth/renderers/*.so $out/renderers
229 # useless in the initrd, and adds several megabytes to the closure
230 rm $out/renderers/x11.so
231 ''
232 );
233 "/etc/plymouth/themes".source = pkgs.runCommand "plymouth-initrd-themes" { } (
234 checkIfThemeExists
235 + ''
236 mkdir -p $out/${cfg.theme}
237 cp -r ${themesEnv}/share/plymouth/themes/${cfg.theme}/* $out/${cfg.theme}
238 # Copy more themes if the theme depends on others
239 for theme in $(grep -hRo '/share/plymouth/themes/.*$' $out | xargs -n1 basename); do
240 if [[ -d "${themesEnv}/share/plymouth/themes/$theme" ]]; then
241 if [[ ! -d "$out/$theme" ]]; then
242 echo "Adding dependent theme: $theme"
243 mkdir -p "$out/$theme"
244 cp -r "${themesEnv}/share/plymouth/themes/$theme"/* "$out/$theme"
245 fi
246 else
247 echo "Missing theme dependency: $theme"
248 fi
249 done
250 # Fixup references
251 for theme in $out/*/*.plymouth; do
252 sed -i "s,${builtins.storeDir}/.*/share/plymouth/themes,$out," "$theme"
253 done
254 ''
255 );
256
257 # Fonts
258 "/etc/plymouth/fonts".source = pkgs.runCommand "plymouth-initrd-fonts" { } ''
259 mkdir -p $out
260 cp ${escapeShellArg cfg.font} $out
261 '';
262 "/etc/fonts/fonts.conf".text = ''
263 <?xml version="1.0"?>
264 <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
265 <fontconfig>
266 <dir>/etc/plymouth/fonts</dir>
267 </fontconfig>
268 '';
269 };
270 # Properly enable units. These are the units that arch copies
271 services = {
272 plymouth-halt.wantedBy = [ "halt.target" ];
273 plymouth-kexec.wantedBy = [ "kexec.target" ];
274 plymouth-poweroff.wantedBy = [ "poweroff.target" ];
275 plymouth-quit-wait.wantedBy = [ "multi-user.target" ];
276 plymouth-quit.wantedBy = [ "multi-user.target" ];
277 plymouth-read-write.wantedBy = [ "sysinit.target" ];
278 plymouth-reboot.wantedBy = [ "reboot.target" ];
279 plymouth-start.wantedBy = [
280 "initrd-switch-root.target"
281 "sysinit.target"
282 ];
283 plymouth-switch-root-initramfs.wantedBy = [
284 "halt.target"
285 "kexec.target"
286 "plymouth-switch-root-initramfs.service"
287 "poweroff.target"
288 "reboot.target"
289 ];
290 plymouth-switch-root.wantedBy = [ "initrd-switch-root.target" ];
291 };
292 # Link in runtime files before starting
293 services.plymouth-start.preStart = ''
294 mkdir -p /run/plymouth
295 ln -sf /etc/plymouth/{plymouthd.defaults,themes,plugins} /run/plymouth/
296 '';
297 };
298
299 # Insert required udev rules. We take stage 2 systemd because the udev
300 # rules are only generated when building with logind.
301 boot.initrd.services.udev.packages = [
302 (pkgs.runCommand "initrd-plymouth-udev-rules" { } ''
303 mkdir -p $out/etc/udev/rules.d
304 cp ${config.systemd.package.out}/lib/udev/rules.d/{70-uaccess,71-seat}.rules $out/etc/udev/rules.d
305 sed -i '/loginctl/d' $out/etc/udev/rules.d/71-seat.rules
306 '')
307 ];
308
309 boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (
310 ''
311 copy_bin_and_libs ${plymouth}/bin/plymouth
312 copy_bin_and_libs ${plymouth}/bin/plymouthd
313
314 ''
315 + checkIfThemeExists
316 + ''
317
318 moduleName="$(sed -n 's,ModuleName *= *,,p' ${themesEnv}/share/plymouth/themes/${cfg.theme}/${cfg.theme}.plymouth)"
319
320 mkdir -p $out/lib/plymouth/renderers
321 # module might come from a theme
322 cp ${themesEnv}/lib/plymouth/*.so $out/lib/plymouth
323 cp ${plymouth}/lib/plymouth/renderers/*.so $out/lib/plymouth/renderers
324 # useless in the initrd, and adds several megabytes to the closure
325 rm $out/lib/plymouth/renderers/x11.so
326
327 mkdir -p $out/share/plymouth/themes
328 cp ${plymouth}/share/plymouth/plymouthd.defaults $out/share/plymouth
329
330 # Copy themes into working directory for patching
331 mkdir themes
332
333 # Use -L to copy the directories proper, not the symlinks to them.
334 # Copy all themes because they're not large assets, and bgrt depends on the ImageDir of
335 # the spinner theme.
336 cp -r -L ${themesEnv}/share/plymouth/themes/* themes
337
338 # Patch out any attempted references to the theme or plymouth's themes directory
339 chmod -R +w themes
340 find themes -type f | while read file
341 do
342 sed -i "s,${builtins.storeDir}/.*/share/plymouth/themes,$out/share/plymouth/themes,g" $file
343 done
344
345 # Install themes
346 cp -r themes/* $out/share/plymouth/themes
347
348 # Install logo
349 mkdir -p $out/etc/plymouth
350 cp -r -L ${themesEnv}/etc/plymouth $out/etc
351
352 # Setup font
353 mkdir -p $out/share/fonts
354 cp ${cfg.font} $out/share/fonts
355 mkdir -p $out/etc/fonts
356 cat > $out/etc/fonts/fonts.conf <<EOF
357 <?xml version="1.0"?>
358 <!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
359 <fontconfig>
360 <dir>$out/share/fonts</dir>
361 </fontconfig>
362 EOF
363 ''
364 );
365
366 boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
367 $out/bin/plymouthd --help >/dev/null
368 $out/bin/plymouth --help >/dev/null
369 '';
370
371 boot.initrd.extraUdevRulesCommands = mkIf (!config.boot.initrd.systemd.enable) ''
372 cp ${config.systemd.package}/lib/udev/rules.d/{70-uaccess,71-seat}.rules $out
373 sed -i '/loginctl/d' $out/71-seat.rules
374 '';
375
376 # We use `mkAfter` to ensure that LUKS password prompt would be shown earlier than the splash screen.
377 boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (mkAfter ''
378 plymouth_enabled=1
379 for o in $(cat /proc/cmdline); do
380 case $o in
381 plymouth.enable=0)
382 plymouth_enabled=0
383 ;;
384 esac
385 done
386
387 if [ "$plymouth_enabled" != 0 ]; then
388 mkdir -p /etc/plymouth
389 mkdir -p /run/plymouth
390 ln -s $extraUtils/etc/plymouth/logo.png /etc/plymouth/logo.png
391 ln -s ${configFile} /etc/plymouth/plymouthd.conf
392 ln -s $extraUtils/share/plymouth/plymouthd.defaults /run/plymouth/plymouthd.defaults
393 ln -s $extraUtils/share/plymouth/themes /run/plymouth/themes
394 ln -s $extraUtils/lib/plymouth /run/plymouth/plugins
395 ln -s $extraUtils/etc/fonts /etc/fonts
396
397 plymouthd --mode=boot --pid-file=/run/plymouth/pid --attach-to-session
398 plymouth show-splash
399 fi
400 '');
401
402 boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) ''
403 if [ "$plymouth_enabled" != 0 ]; then
404 plymouth update-root-fs --new-root-dir="$targetRoot"
405 fi
406 '';
407
408 # `mkBefore` to ensure that any custom prompts would be visible.
409 boot.initrd.preFailCommands = mkIf (!config.boot.initrd.systemd.enable) (mkBefore ''
410 if [ "$plymouth_enabled" != 0 ]; then
411 plymouth quit --wait
412 fi
413 '');
414
415 };
416
417}