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