From 2d4ebf1259149ac52c191f461eef4eae6c3671fc Mon Sep 17 00:00:00 2001 From: Will Fancher Date: Sat, 19 Mar 2022 23:00:06 -0400 Subject: [PATCH] initrd: Optional systemd-based initrd --- nixos/lib/systemd-lib.nix | 29 +- nixos/lib/systemd-types.nix | 1 + nixos/modules/module-list.nix | 1 + nixos/modules/system/boot/stage-1.nix | 8 +- nixos/modules/system/boot/systemd.nix | 3 - nixos/modules/system/boot/systemd/initrd.nix | 355 +++++++++++++++++++ pkgs/os-specific/linux/systemd/default.nix | 2 +- 7 files changed, 385 insertions(+), 14 deletions(-) create mode 100644 nixos/modules/system/boot/systemd/initrd.nix diff --git a/nixos/lib/systemd-lib.nix b/nixos/lib/systemd-lib.nix index 27c8b5b04713..3129fbe7bdb9 100644 --- a/nixos/lib/systemd-lib.nix +++ b/nixos/lib/systemd-lib.nix @@ -291,16 +291,10 @@ in rec { }; }; - serviceConfig = { name, config, ... }: { + mkServiceConfig = path: { name, config, ... }: { config = mkMerge [ { # Default path for systemd services. Should be quite minimal. - path = mkAfter - [ pkgs.coreutils - pkgs.findutils - pkgs.gnugrep - pkgs.gnused - systemd - ]; + path = mkAfter path; environment.PATH = "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}"; } (mkIf (config.preStart != "") @@ -330,6 +324,16 @@ in rec { ]; }; + serviceConfig = mkServiceConfig [ + pkgs.coreutils + pkgs.findutils + pkgs.gnugrep + pkgs.gnused + systemd + ]; + + initrdServiceConfig = mkServiceConfig []; + mountConfig = { config, ... }: { config = { mountConfig = @@ -387,6 +391,15 @@ in rec { ''; }; + initrdServiceToUnit = name: def: + { inherit (def) aliases wantedBy requiredBy enable; + text = commonUnitText def + + '' + [Service] + ${attrsToSection def.serviceConfig} + ''; + }; + socketToUnit = name: def: { inherit (def) aliases wantedBy requiredBy enable; text = commonUnitText def + diff --git a/nixos/lib/systemd-types.nix b/nixos/lib/systemd-types.nix index a7a324f187c2..b303335ffc1f 100644 --- a/nixos/lib/systemd-types.nix +++ b/nixos/lib/systemd-types.nix @@ -12,6 +12,7 @@ rec { })); services = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ]); + initrdServices = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig initrdServiceConfig ]); targets = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig ]); diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 49d1105247ab..2c1272ecd5a5 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1172,6 +1172,7 @@ ./system/boot/systemd/nspawn.nix ./system/boot/systemd/tmpfiles.nix ./system/boot/systemd/user.nix + ./system/boot/systemd/initrd.nix ./system/boot/timesyncd.nix ./system/boot/tmp.nix ./system/etc/etc-activation.nix diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix index 1575c0257d1c..aa2f4fb860b7 100644 --- a/nixos/modules/system/boot/stage-1.nix +++ b/nixos/modules/system/boot/stage-1.nix @@ -706,8 +706,12 @@ in } ]; - system.build = - { inherit bootStage1 initialRamdisk initialRamdiskSecretAppender extraUtils; }; + system.build = mkMerge [ + { inherit bootStage1 initialRamdiskSecretAppender extraUtils; } + + # generated in nixos/modules/system/boot/systemd/initrd.nix + (mkIf (!config.boot.initrd.systemd.enable) { inherit initialRamdisk; }) + ]; system.requiredKernelConfig = with config.lib.kernelConfig; [ (isYes "TMPFS") diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix index 00116c73ccc6..fd5500773626 100644 --- a/nixos/modules/system/boot/systemd.nix +++ b/nixos/modules/system/boot/systemd.nix @@ -11,10 +11,7 @@ let systemd = cfg.package; inherit (systemdUtils.lib) - makeUnit generateUnits - makeJobScript - commonUnitText targetToUnit serviceToUnit socketToUnit diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix new file mode 100644 index 000000000000..cd626a915a41 --- /dev/null +++ b/nixos/modules/system/boot/systemd/initrd.nix @@ -0,0 +1,355 @@ +{ lib, config, utils, pkgs, ... }: + +with lib; + +let + inherit (utils) systemdUtils escapeSystemdPath; + inherit (systemdUtils.lib) + generateUnits + pathToUnit + initrdServiceToUnit + sliceToUnit + socketToUnit + targetToUnit + timerToUnit + mountToUnit + automountToUnit; + + + cfg = config.boot.initrd.systemd; + + # Copied from fedora + upstreamUnits = [ + "basic.target" + "ctrl-alt-del.target" + "emergency.service" + "emergency.target" + "final.target" + "halt.target" + "initrd-cleanup.service" + "initrd-fs.target" + "initrd-parse-etc.service" + "initrd-root-device.target" + "initrd-root-fs.target" + "initrd-switch-root.service" + "initrd-switch-root.target" + "initrd.target" + "initrd-udevadm-cleanup-db.service" + "kexec.target" + "kmod-static-nodes.service" + "local-fs-pre.target" + "local-fs.target" + "multi-user.target" + "paths.target" + "poweroff.target" + "reboot.target" + "rescue.service" + "rescue.target" + "rpcbind.target" + "shutdown.target" + "sigpwr.target" + "slices.target" + "sockets.target" + "swap.target" + "sysinit.target" + "sys-kernel-config.mount" + "syslog.socket" + "systemd-ask-password-console.path" + "systemd-ask-password-console.service" + "systemd-fsck@.service" + "systemd-halt.service" + "systemd-hibernate-resume@.service" + "systemd-journald-audit.socket" + "systemd-journald-dev-log.socket" + "systemd-journald.service" + "systemd-journald.socket" + "systemd-kexec.service" + "systemd-modules-load.service" + "systemd-poweroff.service" + "systemd-random-seed.service" + "systemd-reboot.service" + "systemd-sysctl.service" + "systemd-tmpfiles-setup-dev.service" + "systemd-tmpfiles-setup.service" + "systemd-udevd-control.socket" + "systemd-udevd-kernel.socket" + "systemd-udevd.service" + "systemd-udev-settle.service" + "systemd-udev-trigger.service" + "systemd-vconsole-setup.service" + "timers.target" + "umount.target" + + # TODO: Networking + # "network-online.target" + # "network-pre.target" + # "network.target" + # "nss-lookup.target" + # "nss-user-lookup.target" + # "remote-fs-pre.target" + # "remote-fs.target" + ] ++ cfg.additionalUpstreamUnits; + + upstreamWants = [ + "sysinit.target.wants" + ]; + + enabledUpstreamUnits = filter (n: ! elem n cfg.suppressedUnits) upstreamUnits; + enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedUnits) cfg.units; + + stage1Units = generateUnits { + type = "initrd"; + units = enabledUnits; + upstreamUnits = enabledUpstreamUnits; + inherit upstreamWants; + inherit (cfg) packages package; + }; + + fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems; + + fstab = pkgs.writeText "fstab" (lib.concatMapStringsSep "\n" + ({ fsType, mountPoint, device, options, ... }: + "${device} /sysroot${mountPoint} ${fsType} ${lib.concatStringsSep "," options}") fileSystems); + + kernel-name = config.boot.kernelPackages.kernel.name or "kernel"; + modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; }; + firmware = config.hardware.firmware; + # Determine the set of modules that we need to mount the root FS. + modulesClosure = pkgs.makeModulesClosure { + rootModules = config.boot.initrd.availableKernelModules ++ config.boot.initrd.kernelModules; + kernel = modulesTree; + firmware = firmware; + allowMissing = false; + }; + + initrdBinEnv = pkgs.buildEnv { + name = "initrd-emergency-env"; + paths = map getBin cfg.initrdBin; + pathsToLink = ["/bin" "/sbin"]; + }; + + initialRamdisk = pkgs.makeInitrdNG { + contents = cfg.objects; + }; + +in { + options.boot.initrd.systemd = { + enable = mkEnableOption ''systemd in initrd. + + Note: This is in very early development and is highly + experimental. Most of the features NixOS supports in initrd are + not yet supported by the intrd generated with this option. + ''; + + package = (lib.mkPackageOption pkgs "systemd" { + default = "systemdMinimal"; + }) // { + visible = false; + }; + + objects = mkOption { + description = "List of objects to include in the initrd, and their symlinks"; + example = literalExpression '' + [ { object = "''${systemd}/lib/systemd/systemd"; symlink = "/init"; } ] + ''; + visible = false; + type = types.listOf (types.submodule { + options = { + object = mkOption { + type = types.path; + description = "The object to include in initrd."; + }; + symlink = mkOption { + type = types.nullOr types.path; + description = "A symlink to create in initrd pointing to the object."; + default = null; + }; + }; + }); + }; + + emergencyHashedPassword = mkOption { + type = types.str; + visible = false; + description = '' + Hashed password for the super user account in stage 1 emergency mode + + Blank for no password, ! for super user disabled. + ''; + default = "!"; + }; + + initrdBin = mkOption { + type = types.listOf types.package; + default = []; + visible = false; + description = '' + Packages to include in /bin for the stage 1 emergency shell. + ''; + }; + + additionalUpstreamUnits = mkOption { + default = [ ]; + type = types.listOf types.str; + visible = false; + example = [ "debug-shell.service" "systemd-quotacheck.service" ]; + description = '' + Additional units shipped with systemd that shall be enabled. + ''; + }; + + suppressedUnits = mkOption { + default = [ ]; + type = types.listOf types.str; + example = [ "systemd-backlight@.service" ]; + visible = false; + description = '' + A list of units to suppress when generating system systemd configuration directory. This has + priority over upstream units, , and + . The main purpose of this is to + suppress a upstream systemd unit with any modifications made to it by other NixOS modules. + ''; + }; + + units = mkOption { + description = "Definition of systemd units."; + default = {}; + visible = false; + type = systemdUtils.types.units; + }; + + packages = mkOption { + default = []; + visible = false; + type = types.listOf types.package; + example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]"; + description = "Packages providing systemd units and hooks."; + }; + + targets = mkOption { + default = {}; + visible = false; + type = systemdUtils.types.targets; + description = "Definition of systemd target units."; + }; + + services = mkOption { + default = {}; + type = systemdUtils.types.initrdServices; + visible = false; + description = "Definition of systemd service units."; + }; + + sockets = mkOption { + default = {}; + type = systemdUtils.types.sockets; + visible = false; + description = "Definition of systemd socket units."; + }; + + timers = mkOption { + default = {}; + type = systemdUtils.types.timers; + visible = false; + description = "Definition of systemd timer units."; + }; + + paths = mkOption { + default = {}; + type = systemdUtils.types.paths; + visible = false; + description = "Definition of systemd path units."; + }; + + mounts = mkOption { + default = []; + type = systemdUtils.types.mounts; + visible = false; + description = '' + Definition of systemd mount units. + This is a list instead of an attrSet, because systemd mandates the names to be derived from + the 'where' attribute. + ''; + }; + + automounts = mkOption { + default = []; + type = systemdUtils.types.automounts; + visible = false; + description = '' + Definition of systemd automount units. + This is a list instead of an attrSet, because systemd mandates the names to be derived from + the 'where' attribute. + ''; + }; + + slices = mkOption { + default = {}; + type = systemdUtils.types.slices; + visible = false; + description = "Definition of slice configurations."; + }; + }; + + config = mkIf (config.boot.initrd.enable && cfg.enable) { + system.build = { inherit initialRamdisk; }; + boot.initrd.systemd = { + initrdBin = [pkgs.bash pkgs.coreutils pkgs.kmod cfg.package]; + + objects = [ + { object = "${cfg.package}/lib/systemd/systemd"; symlink = "/init"; } + { object = stage1Units; symlink = "/etc/systemd/system"; } + + # TODO: Limit this to the bare necessities + { object = "${cfg.package}/lib"; } + + { object = "${cfg.package.util-linux}/bin/mount"; } + { object = "${cfg.package.util-linux}/bin/umount"; } + { object = "${cfg.package.util-linux}/bin/sulogin"; } + + # TODO: Not sure why this needs to be here for the recovery shell to work + { object = "${pkgs.glibc}/lib/libnss_files.so"; } + + { object = config.environment.etc.os-release.source; symlink = "/etc/initrd-release"; } + { object = config.environment.etc.os-release.source; symlink = "/etc/os-release"; } + { object = fstab; symlink = "/etc/fstab"; } + { + object = "${modulesClosure}/lib/modules"; + symlink = "/lib/modules"; + } + { + symlink = "/etc/modules-load.d/nixos.conf"; + object = pkgs.writeText "nixos.conf" + (lib.concatStringsSep "\n" config.boot.initrd.kernelModules); + } + { + object = builtins.toFile "passwd" "root:x:0:0:System Administrator:/root:/bin/bash"; + symlink = "/etc/passwd"; + } + { + object = builtins.toFile "shadow" "root:${config.boot.initrd.systemd.emergencyHashedPassword}:::::::"; + symlink = "/etc/shadow"; + } + { object = "${initrdBinEnv}/bin"; symlink = "/bin"; } + { object = "${initrdBinEnv}/sbin"; symlink = "/sbin"; } + { object = builtins.toFile "bashrc" "PATH=/bin:/sbin"; symlink = "/etc/bashrc"; } + { object = builtins.toFile "sysctl.conf" "kernel.modprobe = /sbin/modprobe"; symlink = "/etc/sysctl.d/nixos.conf"; } + ]; + + targets.initrd.aliases = ["default.target"]; + units = + mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths + // mapAttrs' (n: v: nameValuePair "${n}.service" (initrdServiceToUnit n v)) cfg.services + // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices + // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets + // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets + // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers + // listToAttrs (map + (v: let n = escapeSystemdPath v.where; + in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts) + // listToAttrs (map + (v: let n = escapeSystemdPath v.where; + in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts); + }; + }; +} diff --git a/pkgs/os-specific/linux/systemd/default.nix b/pkgs/os-specific/linux/systemd/default.nix index 3a3a419093b7..4cbed9b7cbf1 100644 --- a/pkgs/os-specific/linux/systemd/default.nix +++ b/pkgs/os-specific/linux/systemd/default.nix @@ -603,7 +603,7 @@ stdenv.mkDerivation { # runtime; otherwise we can't and we need to reboot. interfaceVersion = 2; - inherit withCryptsetup; + inherit withCryptsetup util-linux; tests = { inherit (nixosTests) switchTest;