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;