at 23.05-pre 18 kB view raw
1{ lib, 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 # Copied from fedora 22 upstreamUnits = [ 23 "basic.target" 24 "ctrl-alt-del.target" 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 75 # TODO: Networking 76 # "network-online.target" 77 # "network-pre.target" 78 # "network.target" 79 # "nss-lookup.target" 80 # "nss-user-lookup.target" 81 # "remote-fs-pre.target" 82 # "remote-fs.target" 83 ] ++ cfg.additionalUpstreamUnits; 84 85 upstreamWants = [ 86 "sysinit.target.wants" 87 ]; 88 89 enabledUpstreamUnits = filter (n: ! elem n cfg.suppressedUnits) upstreamUnits; 90 enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedUnits) cfg.units; 91 jobScripts = concatLists (mapAttrsToList (_: unit: unit.jobScripts or []) (filterAttrs (_: v: v.enable) cfg.services)); 92 93 stage1Units = generateUnits { 94 type = "initrd"; 95 units = enabledUnits; 96 upstreamUnits = enabledUpstreamUnits; 97 inherit upstreamWants; 98 inherit (cfg) packages package; 99 }; 100 101 fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems; 102 103 needMakefs = lib.any (fs: fs.autoFormat) fileSystems; 104 needGrowfs = lib.any (fs: fs.autoResize) fileSystems; 105 106 kernel-name = config.boot.kernelPackages.kernel.name or "kernel"; 107 modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; }; 108 firmware = config.hardware.firmware; 109 # Determine the set of modules that we need to mount the root FS. 110 modulesClosure = pkgs.makeModulesClosure { 111 rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules; 112 kernel = modulesTree; 113 firmware = firmware; 114 allowMissing = false; 115 }; 116 117 initrdBinEnv = pkgs.buildEnv { 118 name = "initrd-bin-env"; 119 paths = map getBin cfg.initrdBin; 120 pathsToLink = ["/bin" "/sbin"]; 121 postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -s '${v}' $out/bin/'${n}'") cfg.extraBin); 122 }; 123 124 initialRamdisk = pkgs.makeInitrdNG { 125 name = "initrd-${kernel-name}"; 126 inherit (config.boot.initrd) compressor compressorArgs prepend; 127 inherit (cfg) strip; 128 129 contents = map (path: { object = path; symlink = ""; }) (subtractLists cfg.suppressedStorePaths cfg.storePaths) 130 ++ mapAttrsToList (_: v: { object = v.source; symlink = v.target; }) (filterAttrs (_: v: v.enable) cfg.contents); 131 }; 132 133in { 134 options.boot.initrd.systemd = { 135 enable = mkEnableOption (lib.mdDoc "systemd in initrd") // { 136 description = lib.mdDoc '' 137 Whether to enable systemd in initrd. 138 139 Note: This is in very early development and is highly 140 experimental. Most of the features NixOS supports in initrd are 141 not yet supported by the intrd generated with this option. 142 ''; 143 }; 144 145 package = (mkPackageOption pkgs "systemd" { 146 default = "systemdStage1"; 147 }) // { 148 visible = false; 149 }; 150 151 contents = mkOption { 152 description = lib.mdDoc "Set of files that have to be linked into the initrd"; 153 example = literalExpression '' 154 { 155 "/etc/hostname".text = "mymachine"; 156 } 157 ''; 158 visible = false; 159 default = {}; 160 type = utils.systemdUtils.types.initrdContents; 161 }; 162 163 storePaths = mkOption { 164 description = lib.mdDoc '' 165 Store paths to copy into the initrd as well. 166 ''; 167 type = with types; listOf (oneOf [ singleLineStr package ]); 168 default = []; 169 }; 170 171 strip = mkOption { 172 description = lib.mdDoc '' 173 Whether to completely strip executables and libraries copied to the initramfs. 174 175 Setting this to false may save on the order of 30MiB on the 176 machine building the system (by avoiding a binutils 177 reference), at the cost of ~1MiB of initramfs size. This puts 178 this option firmly in the territory of micro-optimisation. 179 ''; 180 type = types.bool; 181 default = true; 182 }; 183 184 extraBin = mkOption { 185 description = lib.mdDoc '' 186 Tools to add to /bin 187 ''; 188 example = literalExpression '' 189 { 190 umount = ''${pkgs.util-linux}/bin/umount; 191 } 192 ''; 193 type = types.attrsOf types.path; 194 default = {}; 195 }; 196 197 suppressedStorePaths = mkOption { 198 description = lib.mdDoc '' 199 Store paths specified in the storePaths option that 200 should not be copied. 201 ''; 202 type = types.listOf types.singleLineStr; 203 default = []; 204 }; 205 206 emergencyAccess = mkOption { 207 type = with types; oneOf [ bool (nullOr (passwdEntry str)) ]; 208 visible = false; 209 description = lib.mdDoc '' 210 Set to true for unauthenticated emergency access, and false for 211 no emergency access. 212 213 Can also be set to a hashed super user password to allow 214 authenticated access to the emergency mode. 215 ''; 216 default = false; 217 }; 218 219 initrdBin = mkOption { 220 type = types.listOf types.package; 221 default = []; 222 visible = false; 223 description = lib.mdDoc '' 224 Packages to include in /bin for the stage 1 emergency shell. 225 ''; 226 }; 227 228 additionalUpstreamUnits = mkOption { 229 default = [ ]; 230 type = types.listOf types.str; 231 visible = false; 232 example = [ "debug-shell.service" "systemd-quotacheck.service" ]; 233 description = lib.mdDoc '' 234 Additional units shipped with systemd that shall be enabled. 235 ''; 236 }; 237 238 suppressedUnits = mkOption { 239 default = [ ]; 240 type = types.listOf types.str; 241 example = [ "systemd-backlight@.service" ]; 242 visible = false; 243 description = lib.mdDoc '' 244 A list of units to skip when generating system systemd configuration directory. This has 245 priority over upstream units, {option}`boot.initrd.systemd.units`, and 246 {option}`boot.initrd.systemd.additionalUpstreamUnits`. The main purpose of this is to 247 prevent a upstream systemd unit from being added to the initrd with any modifications made to it 248 by other NixOS modules. 249 ''; 250 }; 251 252 units = mkOption { 253 description = lib.mdDoc "Definition of systemd units."; 254 default = {}; 255 visible = false; 256 type = systemdUtils.types.units; 257 }; 258 259 packages = mkOption { 260 default = []; 261 visible = false; 262 type = types.listOf types.package; 263 example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]"; 264 description = lib.mdDoc "Packages providing systemd units and hooks."; 265 }; 266 267 targets = mkOption { 268 default = {}; 269 visible = false; 270 type = systemdUtils.types.initrdTargets; 271 description = lib.mdDoc "Definition of systemd target units."; 272 }; 273 274 services = mkOption { 275 default = {}; 276 type = systemdUtils.types.initrdServices; 277 visible = false; 278 description = lib.mdDoc "Definition of systemd service units."; 279 }; 280 281 sockets = mkOption { 282 default = {}; 283 type = systemdUtils.types.initrdSockets; 284 visible = false; 285 description = lib.mdDoc "Definition of systemd socket units."; 286 }; 287 288 timers = mkOption { 289 default = {}; 290 type = systemdUtils.types.initrdTimers; 291 visible = false; 292 description = lib.mdDoc "Definition of systemd timer units."; 293 }; 294 295 paths = mkOption { 296 default = {}; 297 type = systemdUtils.types.initrdPaths; 298 visible = false; 299 description = lib.mdDoc "Definition of systemd path units."; 300 }; 301 302 mounts = mkOption { 303 default = []; 304 type = systemdUtils.types.initrdMounts; 305 visible = false; 306 description = lib.mdDoc '' 307 Definition of systemd mount units. 308 This is a list instead of an attrSet, because systemd mandates the names to be derived from 309 the 'where' attribute. 310 ''; 311 }; 312 313 automounts = mkOption { 314 default = []; 315 type = systemdUtils.types.automounts; 316 visible = false; 317 description = lib.mdDoc '' 318 Definition of systemd automount units. 319 This is a list instead of an attrSet, because systemd mandates the names to be derived from 320 the 'where' attribute. 321 ''; 322 }; 323 324 slices = mkOption { 325 default = {}; 326 type = systemdUtils.types.slices; 327 visible = false; 328 description = lib.mdDoc "Definition of slice configurations."; 329 }; 330 }; 331 332 config = mkIf (config.boot.initrd.enable && cfg.enable) { 333 system.build = { inherit initialRamdisk; }; 334 335 boot.initrd.availableKernelModules = [ 336 "autofs4" # systemd needs this for some features 337 "tpm-tis" "tpm-crb" # systemd-cryptenroll 338 ]; 339 340 boot.initrd.systemd = { 341 initrdBin = [pkgs.bash pkgs.coreutils cfg.package.kmod cfg.package] ++ config.system.fsPackages; 342 extraBin = { 343 less = "${pkgs.less}/bin/less"; 344 mount = "${cfg.package.util-linux}/bin/mount"; 345 umount = "${cfg.package.util-linux}/bin/umount"; 346 }; 347 348 contents = { 349 "/init".source = "${cfg.package}/lib/systemd/systemd"; 350 "/etc/systemd/system".source = stage1Units; 351 352 "/etc/systemd/system.conf".text = '' 353 [Manager] 354 DefaultEnvironment=PATH=/bin:/sbin ${optionalString (isBool cfg.emergencyAccess && cfg.emergencyAccess) "SYSTEMD_SULOGIN_FORCE=1"} 355 ''; 356 357 "/lib/modules".source = "${modulesClosure}/lib/modules"; 358 "/lib/firmware".source = "${modulesClosure}/lib/firmware"; 359 360 "/etc/modules-load.d/nixos.conf".text = concatStringsSep "\n" config.boot.initrd.kernelModules; 361 362 "/etc/passwd".source = "${pkgs.fakeNss}/etc/passwd"; 363 "/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then "!" else cfg.emergencyAccess}:::::::"; 364 365 "/bin".source = "${initrdBinEnv}/bin"; 366 "/sbin".source = "${initrdBinEnv}/sbin"; 367 368 "/etc/sysctl.d/nixos.conf".text = "kernel.modprobe = /sbin/modprobe"; 369 "/etc/modprobe.d/systemd.conf".source = "${cfg.package}/lib/modprobe.d/systemd.conf"; 370 "/etc/modprobe.d/ubuntu.conf".source = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" { } '' 371 ${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out 372 ''; 373 "/etc/modprobe.d/debian.conf".source = pkgs.kmod-debian-aliases; 374 375 "/etc/os-release".source = config.boot.initrd.osRelease; 376 "/etc/initrd-release".source = config.boot.initrd.osRelease; 377 378 } // optionalAttrs (config.environment.etc ? "modprobe.d/nixos.conf") { 379 "/etc/modprobe.d/nixos.conf".source = config.environment.etc."modprobe.d/nixos.conf".source; 380 }; 381 382 storePaths = [ 383 # systemd tooling 384 "${cfg.package}/lib/systemd/systemd-fsck" 385 (lib.mkIf needGrowfs "${cfg.package}/lib/systemd/systemd-growfs") 386 "${cfg.package}/lib/systemd/systemd-hibernate-resume" 387 "${cfg.package}/lib/systemd/systemd-journald" 388 (lib.mkIf needMakefs "${cfg.package}/lib/systemd/systemd-makefs") 389 "${cfg.package}/lib/systemd/systemd-modules-load" 390 "${cfg.package}/lib/systemd/systemd-remount-fs" 391 "${cfg.package}/lib/systemd/systemd-shutdown" 392 "${cfg.package}/lib/systemd/systemd-sulogin-shell" 393 "${cfg.package}/lib/systemd/systemd-sysctl" 394 395 # generators 396 "${cfg.package}/lib/systemd/system-generators/systemd-debug-generator" 397 "${cfg.package}/lib/systemd/system-generators/systemd-fstab-generator" 398 "${cfg.package}/lib/systemd/system-generators/systemd-gpt-auto-generator" 399 "${cfg.package}/lib/systemd/system-generators/systemd-hibernate-resume-generator" 400 "${cfg.package}/lib/systemd/system-generators/systemd-run-generator" 401 402 # utilities needed by systemd 403 "${cfg.package.util-linux}/bin/mount" 404 "${cfg.package.util-linux}/bin/umount" 405 "${cfg.package.util-linux}/bin/sulogin" 406 407 # so NSS can look up usernames 408 "${pkgs.glibc}/lib/libnss_files.so.2" 409 ] ++ optionals cfg.package.withCryptsetup [ 410 # tpm2 support 411 "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-tpm2.so" 412 pkgs.tpm2-tss 413 414 # fido2 support 415 "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so" 416 "${pkgs.libfido2}/lib/libfido2.so.1" 417 418 # the unwrapped systemd-cryptsetup executable 419 "${cfg.package}/lib/systemd/.systemd-cryptsetup-wrapped" 420 ] ++ jobScripts; 421 422 targets.initrd.aliases = ["default.target"]; 423 units = 424 mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths 425 // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services 426 // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices 427 // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets 428 // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets 429 // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers 430 // listToAttrs (map 431 (v: let n = escapeSystemdPath v.where; 432 in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts) 433 // listToAttrs (map 434 (v: let n = escapeSystemdPath v.where; 435 in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts); 436 437 # The unit in /run/systemd/generator shadows the unit in 438 # /etc/systemd/system, but will still apply drop-ins from 439 # /etc/systemd/system/foo.service.d/ 440 # 441 # We need IgnoreOnIsolate, otherwise the Requires dependency of 442 # a mount unit on its makefs unit causes it to be unmounted when 443 # we isolate for switch-root. Use a dummy package so that 444 # generateUnits will generate drop-ins instead of unit files. 445 packages = [(pkgs.runCommand "dummy" {} '' 446 mkdir -p $out/etc/systemd/system 447 touch $out/etc/systemd/system/systemd-{makefs,growfs}@.service 448 '')]; 449 services."systemd-makefs@" = lib.mkIf needMakefs { unitConfig.IgnoreOnIsolate = true; }; 450 services."systemd-growfs@" = lib.mkIf needGrowfs { unitConfig.IgnoreOnIsolate = true; }; 451 452 # make sure all the /dev nodes are set up 453 services.systemd-tmpfiles-setup-dev.wantedBy = ["sysinit.target"]; 454 455 services.initrd-nixos-activation = { 456 after = [ "initrd-fs.target" ]; 457 requiredBy = [ "initrd.target" ]; 458 unitConfig.AssertPathExists = "/etc/initrd-release"; 459 serviceConfig.Type = "oneshot"; 460 description = "NixOS Activation"; 461 462 script = /* bash */ '' 463 set -uo pipefail 464 export PATH="/bin:${cfg.package.util-linux}/bin" 465 466 # Figure out what closure to boot 467 closure= 468 for o in $(< /proc/cmdline); do 469 case $o in 470 init=*) 471 IFS== read -r -a initParam <<< "$o" 472 closure="$(dirname "''${initParam[1]}")" 473 ;; 474 esac 475 done 476 477 # Sanity check 478 if [ -z "''${closure:-}" ]; then 479 echo 'No init= parameter on the kernel command line' >&2 480 exit 1 481 fi 482 483 # If we are not booting a NixOS closure (e.g. init=/bin/sh), 484 # we don't know what root to prepare so we don't do anything 485 if ! [ -x "/sysroot$closure/prepare-root" ]; then 486 echo "NEW_INIT=''${initParam[1]}" > /etc/switch-root.conf 487 echo "$closure does not look like a NixOS installation - not activating" 488 exit 0 489 fi 490 echo 'NEW_INIT=' > /etc/switch-root.conf 491 492 493 # We need to propagate /run for things like /run/booted-system 494 # and /run/current-system. 495 mkdir -p /sysroot/run 496 mount --bind /run /sysroot/run 497 498 # Initialize the system 499 export IN_NIXOS_SYSTEMD_STAGE1=true 500 exec chroot /sysroot $closure/prepare-root 501 ''; 502 }; 503 504 # This will either call systemctl with the new init as the last parameter (which 505 # is the case when not booting a NixOS system) or with an empty string, causing 506 # systemd to bypass its verification code that checks whether the next file is a systemd 507 # and using its compiled-in value 508 services.initrd-switch-root.serviceConfig = { 509 EnvironmentFile = "-/etc/switch-root.conf"; 510 ExecStart = [ 511 "" 512 ''systemctl --no-block switch-root /sysroot "''${NEW_INIT}"'' 513 ]; 514 }; 515 516 services.panic-on-fail = { 517 wantedBy = ["emergency.target"]; 518 unitConfig = { 519 DefaultDependencies = false; 520 ConditionKernelCommandLine = [ 521 "|boot.panic_on_fail" 522 "|stage1panic" 523 ]; 524 }; 525 script = '' 526 echo c > /proc/sysrq-trigger 527 ''; 528 serviceConfig.Type = "oneshot"; 529 }; 530 }; 531 532 boot.kernelParams = lib.mkIf (config.boot.resumeDevice != "") [ "resume=${config.boot.resumeDevice}" ]; 533 }; 534}