at 24.11-pre 21 kB view raw
1{ lib, options, config, utils, pkgs, ... }: 2 3with lib; 4 5let 6 inherit (utils) systemdUtils escapeSystemdPath; 7 inherit (systemdUtils.lib) 8 generateUnits 9 pathToUnit 10 serviceToUnit 11 sliceToUnit 12 socketToUnit 13 targetToUnit 14 timerToUnit 15 mountToUnit 16 automountToUnit; 17 18 19 cfg = config.boot.initrd.systemd; 20 21 upstreamUnits = [ 22 "basic.target" 23 "ctrl-alt-del.target" 24 "debug-shell.service" 25 "emergency.service" 26 "emergency.target" 27 "final.target" 28 "halt.target" 29 "initrd-cleanup.service" 30 "initrd-fs.target" 31 "initrd-parse-etc.service" 32 "initrd-root-device.target" 33 "initrd-root-fs.target" 34 "initrd-switch-root.service" 35 "initrd-switch-root.target" 36 "initrd.target" 37 "kexec.target" 38 "kmod-static-nodes.service" 39 "local-fs-pre.target" 40 "local-fs.target" 41 "multi-user.target" 42 "paths.target" 43 "poweroff.target" 44 "reboot.target" 45 "rescue.service" 46 "rescue.target" 47 "rpcbind.target" 48 "shutdown.target" 49 "sigpwr.target" 50 "slices.target" 51 "sockets.target" 52 "swap.target" 53 "sysinit.target" 54 "sys-kernel-config.mount" 55 "syslog.socket" 56 "systemd-ask-password-console.path" 57 "systemd-ask-password-console.service" 58 "systemd-fsck@.service" 59 "systemd-halt.service" 60 "systemd-hibernate-resume.service" 61 "systemd-journald-audit.socket" 62 "systemd-journald-dev-log.socket" 63 "systemd-journald.service" 64 "systemd-journald.socket" 65 "systemd-kexec.service" 66 "systemd-modules-load.service" 67 "systemd-poweroff.service" 68 "systemd-reboot.service" 69 "systemd-sysctl.service" 70 "systemd-tmpfiles-setup-dev.service" 71 "systemd-tmpfiles-setup.service" 72 "timers.target" 73 "umount.target" 74 "systemd-bsod.service" 75 ] ++ cfg.additionalUpstreamUnits; 76 77 upstreamWants = [ 78 "sysinit.target.wants" 79 ]; 80 81 enabledUpstreamUnits = filter (n: ! elem n cfg.suppressedUnits) upstreamUnits; 82 enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedUnits) cfg.units; 83 jobScripts = concatLists (mapAttrsToList (_: unit: unit.jobScripts or []) (filterAttrs (_: v: v.enable) cfg.services)); 84 85 stage1Units = generateUnits { 86 type = "initrd"; 87 units = enabledUnits; 88 upstreamUnits = enabledUpstreamUnits; 89 inherit upstreamWants; 90 inherit (cfg) packages package; 91 }; 92 93 kernel-name = config.boot.kernelPackages.kernel.name or "kernel"; 94 # Determine the set of modules that we need to mount the root FS. 95 modulesClosure = pkgs.makeModulesClosure { 96 rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules; 97 kernel = config.system.modulesTree; 98 firmware = config.hardware.firmware; 99 allowMissing = false; 100 }; 101 102 initrdBinEnv = pkgs.buildEnv { 103 name = "initrd-bin-env"; 104 paths = map getBin cfg.initrdBin; 105 pathsToLink = ["/bin" "/sbin"]; 106 postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -sf '${v}' $out/bin/'${n}'") cfg.extraBin); 107 }; 108 109 initialRamdisk = pkgs.makeInitrdNG { 110 name = "initrd-${kernel-name}"; 111 inherit (config.boot.initrd) compressor compressorArgs prepend; 112 inherit (cfg) strip; 113 114 contents = map (path: { object = path; symlink = ""; }) (subtractLists cfg.suppressedStorePaths cfg.storePaths) 115 ++ mapAttrsToList (_: v: { object = v.source; symlink = v.target; }) (filterAttrs (_: v: v.enable) cfg.contents); 116 }; 117 118in { 119 options.boot.initrd.systemd = { 120 enable = mkEnableOption "systemd in initrd" // { 121 description = '' 122 Whether to enable systemd in initrd. The unit options such as 123 {option}`boot.initrd.systemd.services` are the same as their 124 stage 2 counterparts such as {option}`systemd.services`, 125 except that `restartTriggers` and `reloadTriggers` are not 126 supported. 127 ''; 128 }; 129 130 package = lib.mkOption { 131 type = lib.types.package; 132 default = config.systemd.package; 133 defaultText = lib.literalExpression "config.systemd.package"; 134 description = '' 135 The systemd package to use. 136 ''; 137 }; 138 139 extraConfig = mkOption { 140 default = ""; 141 type = types.lines; 142 example = "DefaultLimitCORE=infinity"; 143 description = '' 144 Extra config options for systemd. See systemd-system.conf(5) man page 145 for available options. 146 ''; 147 }; 148 149 managerEnvironment = mkOption { 150 type = with types; attrsOf (nullOr (oneOf [ str path package ])); 151 default = {}; 152 example = { SYSTEMD_LOG_LEVEL = "debug"; }; 153 description = '' 154 Environment variables of PID 1. These variables are 155 *not* passed to started units. 156 ''; 157 }; 158 159 contents = mkOption { 160 description = "Set of files that have to be linked into the initrd"; 161 example = literalExpression '' 162 { 163 "/etc/hostname".text = "mymachine"; 164 } 165 ''; 166 default = {}; 167 type = utils.systemdUtils.types.initrdContents; 168 }; 169 170 storePaths = mkOption { 171 description = '' 172 Store paths to copy into the initrd as well. 173 ''; 174 type = with types; listOf (oneOf [ singleLineStr package ]); 175 default = []; 176 }; 177 178 strip = mkOption { 179 description = '' 180 Whether to completely strip executables and libraries copied to the initramfs. 181 182 Setting this to false may save on the order of 30MiB on the 183 machine building the system (by avoiding a binutils 184 reference), at the cost of ~1MiB of initramfs size. This puts 185 this option firmly in the territory of micro-optimisation. 186 ''; 187 type = types.bool; 188 default = true; 189 }; 190 191 extraBin = mkOption { 192 description = '' 193 Tools to add to /bin 194 ''; 195 example = literalExpression '' 196 { 197 umount = ''${pkgs.util-linux}/bin/umount; 198 } 199 ''; 200 type = types.attrsOf types.path; 201 default = {}; 202 }; 203 204 suppressedStorePaths = mkOption { 205 description = '' 206 Store paths specified in the storePaths option that 207 should not be copied. 208 ''; 209 type = types.listOf types.singleLineStr; 210 default = []; 211 }; 212 213 root = lib.mkOption { 214 type = lib.types.enum [ "fstab" "gpt-auto" ]; 215 default = "fstab"; 216 example = "gpt-auto"; 217 description = '' 218 Controls how systemd will interpret the root FS in initrd. See 219 {manpage}`kernel-command-line(7)`. NixOS currently does not 220 allow specifying the root file system itself this 221 way. Instead, the `fstab` value is used in order to interpret 222 the root file system specified with the `fileSystems` option. 223 ''; 224 }; 225 226 emergencyAccess = mkOption { 227 type = with types; oneOf [ bool (nullOr (passwdEntry str)) ]; 228 description = '' 229 Set to true for unauthenticated emergency access, and false for 230 no emergency access. 231 232 Can also be set to a hashed super user password to allow 233 authenticated access to the emergency mode. 234 ''; 235 default = false; 236 }; 237 238 initrdBin = mkOption { 239 type = types.listOf types.package; 240 default = []; 241 description = '' 242 Packages to include in /bin for the stage 1 emergency shell. 243 ''; 244 }; 245 246 additionalUpstreamUnits = mkOption { 247 default = [ ]; 248 type = types.listOf types.str; 249 example = [ "debug-shell.service" "systemd-quotacheck.service" ]; 250 description = '' 251 Additional units shipped with systemd that shall be enabled. 252 ''; 253 }; 254 255 suppressedUnits = mkOption { 256 default = [ ]; 257 type = types.listOf types.str; 258 example = [ "systemd-backlight@.service" ]; 259 description = '' 260 A list of units to skip when generating system systemd configuration directory. This has 261 priority over upstream units, {option}`boot.initrd.systemd.units`, and 262 {option}`boot.initrd.systemd.additionalUpstreamUnits`. The main purpose of this is to 263 prevent a upstream systemd unit from being added to the initrd with any modifications made to it 264 by other NixOS modules. 265 ''; 266 }; 267 268 units = mkOption { 269 description = "Definition of systemd units."; 270 default = {}; 271 visible = "shallow"; 272 type = systemdUtils.types.units; 273 }; 274 275 packages = mkOption { 276 default = []; 277 type = types.listOf types.package; 278 example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]"; 279 description = "Packages providing systemd units and hooks."; 280 }; 281 282 targets = mkOption { 283 default = {}; 284 visible = "shallow"; 285 type = systemdUtils.types.initrdTargets; 286 description = "Definition of systemd target units."; 287 }; 288 289 services = mkOption { 290 default = {}; 291 type = systemdUtils.types.initrdServices; 292 visible = "shallow"; 293 description = "Definition of systemd service units."; 294 }; 295 296 sockets = mkOption { 297 default = {}; 298 type = systemdUtils.types.initrdSockets; 299 visible = "shallow"; 300 description = "Definition of systemd socket units."; 301 }; 302 303 timers = mkOption { 304 default = {}; 305 type = systemdUtils.types.initrdTimers; 306 visible = "shallow"; 307 description = "Definition of systemd timer units."; 308 }; 309 310 paths = mkOption { 311 default = {}; 312 type = systemdUtils.types.initrdPaths; 313 visible = "shallow"; 314 description = "Definition of systemd path units."; 315 }; 316 317 mounts = mkOption { 318 default = []; 319 type = systemdUtils.types.initrdMounts; 320 visible = "shallow"; 321 description = '' 322 Definition of systemd mount units. 323 This is a list instead of an attrSet, because systemd mandates the names to be derived from 324 the 'where' attribute. 325 ''; 326 }; 327 328 automounts = mkOption { 329 default = []; 330 type = systemdUtils.types.automounts; 331 visible = "shallow"; 332 description = '' 333 Definition of systemd automount units. 334 This is a list instead of an attrSet, because systemd mandates the names to be derived from 335 the 'where' attribute. 336 ''; 337 }; 338 339 slices = mkOption { 340 default = {}; 341 type = systemdUtils.types.slices; 342 visible = "shallow"; 343 description = "Definition of slice configurations."; 344 }; 345 346 enableTpm2 = mkOption { 347 default = true; 348 type = types.bool; 349 description = '' 350 Whether to enable TPM2 support in the initrd. 351 ''; 352 }; 353 }; 354 355 config = mkIf (config.boot.initrd.enable && cfg.enable) { 356 assertions = [ 357 { 358 assertion = cfg.root == "fstab" -> any (fs: fs.mountPoint == "/") (builtins.attrValues config.fileSystems); 359 message = "The fileSystems option does not specify your root file system."; 360 } 361 ] ++ map (name: { 362 assertion = lib.attrByPath name (throw "impossible") config.boot.initrd == ""; 363 message = '' 364 systemd stage 1 does not support 'boot.initrd.${lib.concatStringsSep "." name}'. Please 365 convert it to analogous systemd units in 'boot.initrd.systemd'. 366 367 Definitions: 368 ${lib.concatMapStringsSep "\n" ({ file, ... }: " - ${file}") (lib.attrByPath name (throw "impossible") options.boot.initrd).definitionsWithLocations} 369 ''; 370 }) [ 371 [ "preFailCommands" ] 372 [ "preDeviceCommands" ] 373 [ "preLVMCommands" ] 374 [ "postDeviceCommands" ] 375 [ "postResumeCommands" ] 376 [ "postMountCommands" ] 377 [ "extraUdevRulesCommands" ] 378 [ "extraUtilsCommands" ] 379 [ "extraUtilsCommandsTest" ] 380 [ "network" "postCommands" ] 381 ]; 382 383 system.build = { inherit initialRamdisk; }; 384 385 boot.initrd.availableKernelModules = [ 386 # systemd needs this for some features 387 "autofs" 388 # systemd-cryptenroll 389 ] ++ lib.optional cfg.enableTpm2 "tpm-tis" 390 ++ lib.optional (cfg.enableTpm2 && !(pkgs.stdenv.hostPlatform.isRiscV64 || pkgs.stdenv.hostPlatform.isArmv7)) "tpm-crb" 391 ++ lib.optional cfg.package.withEfi "efivarfs"; 392 393 boot.kernelParams = [ 394 "root=${config.boot.initrd.systemd.root}" 395 ] ++ lib.optional (config.boot.resumeDevice != "") "resume=${config.boot.resumeDevice}" 396 # `systemd` mounts root in initrd as read-only unless "rw" is on the kernel command line. 397 # For NixOS activation to succeed, we need to have root writable in initrd. 398 ++ lib.optional (config.boot.initrd.systemd.root == "gpt-auto") "rw"; 399 400 boot.initrd.systemd = { 401 # bashInteractive is easier to use and also required by debug-shell.service 402 initrdBin = [pkgs.bashInteractive pkgs.coreutils cfg.package.kmod cfg.package]; 403 extraBin = { 404 less = "${pkgs.less}/bin/less"; 405 mount = "${cfg.package.util-linux}/bin/mount"; 406 umount = "${cfg.package.util-linux}/bin/umount"; 407 fsck = "${cfg.package.util-linux}/bin/fsck"; 408 }; 409 410 managerEnvironment.PATH = "/bin:/sbin"; 411 412 contents = { 413 "/tmp/.keep".text = "systemd requires the /tmp mount point in the initrd cpio archive"; 414 "/init".source = "${cfg.package}/lib/systemd/systemd"; 415 "/etc/systemd/system".source = stage1Units; 416 417 "/etc/systemd/system.conf".text = '' 418 [Manager] 419 DefaultEnvironment=PATH=/bin:/sbin 420 ${cfg.extraConfig} 421 ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)} 422 ''; 423 424 "/lib".source = "${modulesClosure}/lib"; 425 426 "/etc/modules-load.d/nixos.conf".text = concatStringsSep "\n" config.boot.initrd.kernelModules; 427 428 # We can use either ! or * to lock the root account in the 429 # console, but some software like OpenSSH won't even allow you 430 # to log in with an SSH key if you use ! so we use * instead 431 "/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then optionalString (!cfg.emergencyAccess) "*" else cfg.emergencyAccess}:::::::"; 432 433 "/bin".source = "${initrdBinEnv}/bin"; 434 "/sbin".source = "${initrdBinEnv}/sbin"; 435 436 "/etc/sysctl.d/nixos.conf".text = "kernel.modprobe = /sbin/modprobe"; 437 "/etc/modprobe.d/systemd.conf".source = "${cfg.package}/lib/modprobe.d/systemd.conf"; 438 "/etc/modprobe.d/ubuntu.conf".source = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" { } '' 439 ${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out 440 ''; 441 "/etc/modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases; 442 443 "/etc/os-release".source = config.boot.initrd.osRelease; 444 "/etc/initrd-release".source = config.boot.initrd.osRelease; 445 446 } // optionalAttrs (config.environment.etc ? "modprobe.d/nixos.conf") { 447 "/etc/modprobe.d/nixos.conf".source = config.environment.etc."modprobe.d/nixos.conf".source; 448 }; 449 450 storePaths = [ 451 # systemd tooling 452 "${cfg.package}/lib/systemd/systemd-executor" 453 "${cfg.package}/lib/systemd/systemd-fsck" 454 "${cfg.package}/lib/systemd/systemd-hibernate-resume" 455 "${cfg.package}/lib/systemd/systemd-journald" 456 "${cfg.package}/lib/systemd/systemd-makefs" 457 "${cfg.package}/lib/systemd/systemd-modules-load" 458 "${cfg.package}/lib/systemd/systemd-remount-fs" 459 "${cfg.package}/lib/systemd/systemd-shutdown" 460 "${cfg.package}/lib/systemd/systemd-sulogin-shell" 461 "${cfg.package}/lib/systemd/systemd-sysctl" 462 "${cfg.package}/lib/systemd/systemd-bsod" 463 464 # generators 465 "${cfg.package}/lib/systemd/system-generators/systemd-debug-generator" 466 "${cfg.package}/lib/systemd/system-generators/systemd-fstab-generator" 467 "${cfg.package}/lib/systemd/system-generators/systemd-gpt-auto-generator" 468 "${cfg.package}/lib/systemd/system-generators/systemd-hibernate-resume-generator" 469 "${cfg.package}/lib/systemd/system-generators/systemd-run-generator" 470 471 # utilities needed by systemd 472 "${cfg.package.util-linux}/bin/mount" 473 "${cfg.package.util-linux}/bin/umount" 474 "${cfg.package.util-linux}/bin/sulogin" 475 476 # required for script services, and some tools like xfs still want the sh symlink 477 "${pkgs.bash}/bin" 478 479 # so NSS can look up usernames 480 "${pkgs.glibc}/lib/libnss_files.so.2" 481 ] ++ optionals (cfg.package.withCryptsetup && cfg.enableTpm2) [ 482 # tpm2 support 483 "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-tpm2.so" 484 pkgs.tpm2-tss 485 ] ++ optionals cfg.package.withCryptsetup [ 486 # fido2 support 487 "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so" 488 "${pkgs.libfido2}/lib/libfido2.so.1" 489 ] ++ jobScripts; 490 491 targets.initrd.aliases = ["default.target"]; 492 units = 493 mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit v)) cfg.paths 494 // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit v)) cfg.services 495 // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit v)) cfg.slices 496 // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit v)) cfg.sockets 497 // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit v)) cfg.targets 498 // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit v)) cfg.timers 499 // listToAttrs (map 500 (v: let n = escapeSystemdPath v.where; 501 in nameValuePair "${n}.mount" (mountToUnit v)) cfg.mounts) 502 // listToAttrs (map 503 (v: let n = escapeSystemdPath v.where; 504 in nameValuePair "${n}.automount" (automountToUnit v)) cfg.automounts); 505 506 # make sure all the /dev nodes are set up 507 services.systemd-tmpfiles-setup-dev.wantedBy = ["sysinit.target"]; 508 509 services.initrd-nixos-activation = { 510 after = [ "initrd-fs.target" ]; 511 requiredBy = [ "initrd.target" ]; 512 unitConfig.AssertPathExists = "/etc/initrd-release"; 513 serviceConfig.Type = "oneshot"; 514 description = "NixOS Activation"; 515 516 script = /* bash */ '' 517 set -uo pipefail 518 export PATH="/bin:${cfg.package.util-linux}/bin" 519 520 # Figure out what closure to boot 521 closure= 522 for o in $(< /proc/cmdline); do 523 case $o in 524 init=*) 525 IFS== read -r -a initParam <<< "$o" 526 closure="''${initParam[1]}" 527 ;; 528 esac 529 done 530 531 # Sanity check 532 if [ -z "''${closure:-}" ]; then 533 echo 'No init= parameter on the kernel command line' >&2 534 exit 1 535 fi 536 537 # Resolve symlinks in the init parameter. We need this for some boot loaders 538 # (e.g. boot.loader.generationsDir). 539 closure="$(chroot /sysroot ${pkgs.coreutils}/bin/realpath "$closure")" 540 541 # Assume the directory containing the init script is the closure. 542 closure="$(dirname "$closure")" 543 544 # If we are not booting a NixOS closure (e.g. init=/bin/sh), 545 # we don't know what root to prepare so we don't do anything 546 if ! [ -x "/sysroot$(readlink "/sysroot$closure/prepare-root" || echo "$closure/prepare-root")" ]; then 547 echo "NEW_INIT=''${initParam[1]}" > /etc/switch-root.conf 548 echo "$closure does not look like a NixOS installation - not activating" 549 exit 0 550 fi 551 echo 'NEW_INIT=' > /etc/switch-root.conf 552 553 554 # We need to propagate /run for things like /run/booted-system 555 # and /run/current-system. 556 mkdir -p /sysroot/run 557 mount --bind /run /sysroot/run 558 559 # Initialize the system 560 export IN_NIXOS_SYSTEMD_STAGE1=true 561 exec chroot /sysroot $closure/prepare-root 562 ''; 563 }; 564 565 # This will either call systemctl with the new init as the last parameter (which 566 # is the case when not booting a NixOS system) or with an empty string, causing 567 # systemd to bypass its verification code that checks whether the next file is a systemd 568 # and using its compiled-in value 569 services.initrd-switch-root.serviceConfig = { 570 EnvironmentFile = "-/etc/switch-root.conf"; 571 ExecStart = [ 572 "" 573 ''systemctl --no-block switch-root /sysroot "''${NEW_INIT}"'' 574 ]; 575 }; 576 577 services.panic-on-fail = { 578 wantedBy = ["emergency.target"]; 579 unitConfig = { 580 DefaultDependencies = false; 581 ConditionKernelCommandLine = [ 582 "|boot.panic_on_fail" 583 "|stage1panic" 584 ]; 585 }; 586 script = '' 587 echo c > /proc/sysrq-trigger 588 ''; 589 serviceConfig.Type = "oneshot"; 590 }; 591 }; 592 }; 593}