diff --git a/nixos/lib/systemd-lib.nix b/nixos/lib/systemd-lib.nix
index 37900b0b16f6..16ec47df3a46 100644
--- a/nixos/lib/systemd-lib.nix
+++ b/nixos/lib/systemd-lib.nix
@@ -122,10 +122,15 @@ in rec {
(if isList value then value else [value]))
as));
- generateUnits = generateUnits' true;
-
- generateUnits' = allowCollisions: type: units: upstreamUnits: upstreamWants:
- pkgs.runCommand "${type}-units"
+ generateUnits = { allowCollisions ? true, type, units, upstreamUnits, upstreamWants, packages ? cfg.packages, package ? cfg.package }:
+ let
+ typeDir = ({
+ system = "system";
+ initrd = "system";
+ user = "user";
+ nspawn = "nspawn";
+ }).${type};
+ in pkgs.runCommand "${type}-units"
{ preferLocalBuild = true;
allowSubstitutes = false;
} ''
@@ -133,7 +138,7 @@ in rec {
# Copy the upstream systemd units we're interested in.
for i in ${toString upstreamUnits}; do
- fn=${cfg.package}/example/systemd/${type}/$i
+ fn=${package}/example/systemd/${typeDir}/$i
if ! [ -e $fn ]; then echo "missing $fn"; false; fi
if [ -L $fn ]; then
target="$(readlink "$fn")"
@@ -150,7 +155,7 @@ in rec {
# Copy .wants links, but only those that point to units that
# we're interested in.
for i in ${toString upstreamWants}; do
- fn=${cfg.package}/example/systemd/${type}/$i
+ fn=${package}/example/systemd/${typeDir}/$i
if ! [ -e $fn ]; then echo "missing $fn"; false; fi
x=$out/$(basename $fn)
mkdir $x
@@ -162,14 +167,14 @@ in rec {
done
# Symlink all units provided listed in systemd.packages.
- packages="${toString cfg.packages}"
+ packages="${toString packages}"
# Filter duplicate directories
declare -A unique_packages
for k in $packages ; do unique_packages[$k]=1 ; done
for i in ''${!unique_packages[@]}; do
- for fn in $i/etc/systemd/${type}/* $i/lib/systemd/${type}/*; do
+ for fn in $i/etc/systemd/${typeDir}/* $i/lib/systemd/${typeDir}/*; do
if ! [[ "$fn" =~ .wants$ ]]; then
if [[ -d "$fn" ]]; then
targetDir="$out/$(basename "$fn")"
@@ -270,9 +275,9 @@ in rec {
{ Conflicts = toString config.conflicts; }
// optionalAttrs (config.requisite != [])
{ Requisite = toString config.requisite; }
- // optionalAttrs (config.restartTriggers != [])
+ // optionalAttrs (config ? restartTriggers && config.restartTriggers != [])
{ X-Restart-Triggers = toString config.restartTriggers; }
- // optionalAttrs (config.reloadTriggers != [])
+ // optionalAttrs (config ? reloadTriggers && config.reloadTriggers != [])
{ X-Reload-Triggers = toString config.reloadTriggers; }
// optionalAttrs (config.description != "") {
Description = config.description; }
@@ -288,45 +293,24 @@ in rec {
};
};
- serviceConfig = { name, config, ... }: {
- config = mkMerge
- [ { # Default path for systemd services. Should be quite minimal.
- path = mkAfter
- [ pkgs.coreutils
- pkgs.findutils
- pkgs.gnugrep
- pkgs.gnused
- systemd
- ];
- environment.PATH = "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}";
- }
- (mkIf (config.preStart != "")
- { serviceConfig.ExecStartPre =
- [ (makeJobScript "${name}-pre-start" config.preStart) ];
- })
- (mkIf (config.script != "")
- { serviceConfig.ExecStart =
- makeJobScript "${name}-start" config.script + " " + config.scriptArgs;
- })
- (mkIf (config.postStart != "")
- { serviceConfig.ExecStartPost =
- [ (makeJobScript "${name}-post-start" config.postStart) ];
- })
- (mkIf (config.reload != "")
- { serviceConfig.ExecReload =
- makeJobScript "${name}-reload" config.reload;
- })
- (mkIf (config.preStop != "")
- { serviceConfig.ExecStop =
- makeJobScript "${name}-pre-stop" config.preStop;
- })
- (mkIf (config.postStop != "")
- { serviceConfig.ExecStopPost =
- makeJobScript "${name}-post-stop" config.postStop;
- })
- ];
+ serviceConfig = { config, ... }: {
+ config.environment.PATH = mkIf (config.path != []) "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}";
};
+ stage2ServiceConfig = {
+ imports = [ serviceConfig ];
+ # Default path for systemd services. Should be quite minimal.
+ config.path = mkAfter [
+ pkgs.coreutils
+ pkgs.findutils
+ pkgs.gnugrep
+ pkgs.gnused
+ systemd
+ ];
+ };
+
+ stage1ServiceConfig = serviceConfig;
+
mountConfig = { config, ... }: {
config = {
mountConfig =
@@ -374,12 +358,12 @@ in rec {
# systemd max line length is now 1MiB
# https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af
in if stringLength s >= 1048576 then throw "The value of the environment variable ‘${n}’ in systemd service ‘${name}.service’ is too long." else s) (attrNames env)}
- ${if def.reloadIfChanged then ''
+ ${if def ? reloadIfChanged && def.reloadIfChanged then ''
X-ReloadIfChanged=true
- '' else if !def.restartIfChanged then ''
+ '' else if (def ? restartIfChanged && !def.restartIfChanged) then ''
X-RestartIfChanged=false
'' else ""}
- ${optionalString (!def.stopIfChanged) "X-StopIfChanged=false"}
+ ${optionalString (def ? stopIfChanged && !def.stopIfChanged) "X-StopIfChanged=false"}
${attrsToSection def.serviceConfig}
'';
};
diff --git a/nixos/lib/systemd-types.nix b/nixos/lib/systemd-types.nix
new file mode 100644
index 000000000000..71962fab2e17
--- /dev/null
+++ b/nixos/lib/systemd-types.nix
@@ -0,0 +1,37 @@
+{ lib, systemdUtils }:
+
+with systemdUtils.lib;
+with systemdUtils.unitOptions;
+with lib;
+
+rec {
+ units = with types;
+ attrsOf (submodule ({ name, config, ... }: {
+ options = concreteUnitOptions;
+ config = { unit = mkDefault (systemdUtils.lib.makeUnit name config); };
+ }));
+
+ services = with types; attrsOf (submodule [ stage2ServiceOptions unitConfig stage2ServiceConfig ]);
+ initrdServices = with types; attrsOf (submodule [ stage1ServiceOptions unitConfig stage1ServiceConfig ]);
+
+ targets = with types; attrsOf (submodule [ stage2CommonUnitOptions unitConfig ]);
+ initrdTargets = with types; attrsOf (submodule [ stage1CommonUnitOptions unitConfig ]);
+
+ sockets = with types; attrsOf (submodule [ stage2SocketOptions unitConfig ]);
+ initrdSockets = with types; attrsOf (submodule [ stage1SocketOptions unitConfig ]);
+
+ timers = with types; attrsOf (submodule [ stage2TimerOptions unitConfig ]);
+ initrdTimers = with types; attrsOf (submodule [ stage1TimerOptions unitConfig ]);
+
+ paths = with types; attrsOf (submodule [ stage2PathOptions unitConfig ]);
+ initrdPaths = with types; attrsOf (submodule [ stage1PathOptions unitConfig ]);
+
+ slices = with types; attrsOf (submodule [ stage2SliceOptions unitConfig ]);
+ initrdSlices = with types; attrsOf (submodule [ stage1SliceOptions unitConfig ]);
+
+ mounts = with types; listOf (submodule [ stage2MountOptions unitConfig mountConfig ]);
+ initrdMounts = with types; listOf (submodule [ stage1MountOptions unitConfig mountConfig ]);
+
+ automounts = with types; listOf (submodule [ stage2AutomountOptions unitConfig automountConfig ]);
+ initrdAutomounts = with types; attrsOf (submodule [ stage1AutomountOptions unitConfig automountConfig ]);
+}
diff --git a/nixos/lib/systemd-unit-options.nix b/nixos/lib/systemd-unit-options.nix
index 8029ba0e3f6c..c9d424d39119 100644
--- a/nixos/lib/systemd-unit-options.nix
+++ b/nixos/lib/systemd-unit-options.nix
@@ -94,7 +94,7 @@ in rec {
};
- commonUnitOptions = sharedOptions // {
+ commonUnitOptions = { options = (sharedOptions // {
description = mkOption {
default = "";
@@ -191,27 +191,6 @@ in rec {
'';
};
- restartTriggers = mkOption {
- default = [];
- type = types.listOf types.unspecified;
- description = ''
- An arbitrary list of items such as derivations. If any item
- in the list changes between reconfigurations, the service will
- be restarted.
- '';
- };
-
- reloadTriggers = mkOption {
- default = [];
- type = types.listOf unitOption;
- description = ''
- An arbitrary list of items such as derivations. If any item
- in the list changes between reconfigurations, the service will
- be reloaded. If anything but a reload trigger changes in the
- unit file, the unit will be restarted instead.
- '';
- };
-
onFailure = mkOption {
default = [];
type = types.listOf unitNameType;
@@ -239,10 +218,39 @@ in rec {
'';
};
+ }); };
+
+ stage2CommonUnitOptions = {
+ imports = [
+ commonUnitOptions
+ ];
+
+ options = {
+ restartTriggers = mkOption {
+ default = [];
+ type = types.listOf types.unspecified;
+ description = ''
+ An arbitrary list of items such as derivations. If any item
+ in the list changes between reconfigurations, the service will
+ be restarted.
+ '';
+ };
+
+ reloadTriggers = mkOption {
+ default = [];
+ type = types.listOf unitOption;
+ description = ''
+ An arbitrary list of items such as derivations. If any item
+ in the list changes between reconfigurations, the service will
+ be reloaded. If anything but a reload trigger changes in the
+ unit file, the unit will be restarted instead.
+ '';
+ };
+ };
};
+ stage1CommonUnitOptions = commonUnitOptions;
-
- serviceOptions = commonUnitOptions // {
+ serviceOptions = { options = {
environment = mkOption {
default = {};
@@ -276,121 +284,164 @@ in rec {
'';
};
- script = mkOption {
- type = types.lines;
- default = "";
- description = "Shell commands executed as the service's main process.";
+ }; };
+
+ stage2ServiceOptions = { name, config, ... }: {
+ imports = [
+ stage2CommonUnitOptions
+ serviceOptions
+ ];
+
+ options = {
+ script = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Shell commands executed as the service's main process.";
+ };
+
+ scriptArgs = mkOption {
+ type = types.str;
+ default = "";
+ description = "Arguments passed to the main process script.";
+ };
+
+ preStart = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed before the service's main process
+ is started.
+ '';
+ };
+
+ postStart = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed after the service's main process
+ is started.
+ '';
+ };
+
+ reload = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed when the service's main process
+ is reloaded.
+ '';
+ };
+
+ preStop = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed to stop the service.
+ '';
+ };
+
+ postStop = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands executed after the service's main process
+ has exited.
+ '';
+ };
+
+ restartIfChanged = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether the service should be restarted during a NixOS
+ configuration switch if its definition has changed.
+ '';
+ };
+
+ reloadIfChanged = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether the service should be reloaded during a NixOS
+ configuration switch if its definition has changed. If
+ enabled, the value of is
+ ignored.
+
+ This option should not be used anymore in favor of
+ which allows more granular
+ control of when a service is reloaded and when a service
+ is restarted.
+ '';
+ };
+
+ stopIfChanged = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If set, a changed unit is restarted by calling
+ systemctl stop in the old configuration,
+ then systemctl start in the new one.
+ Otherwise, it is restarted in a single step using
+ systemctl restart in the new configuration.
+ The latter is less correct because it runs the
+ ExecStop commands from the new
+ configuration.
+ '';
+ };
+
+ startAt = mkOption {
+ type = with types; either str (listOf str);
+ default = [];
+ example = "Sun 14:00:00";
+ description = ''
+ Automatically start this unit at the given date/time, which
+ must be in the format described in
+ systemd.time
+ 7. This is equivalent
+ to adding a corresponding timer unit with
+ set to the value given here.
+ '';
+ apply = v: if isList v then v else [ v ];
+ };
};
- scriptArgs = mkOption {
- type = types.str;
- default = "";
- description = "Arguments passed to the main process script.";
- };
-
- preStart = mkOption {
- type = types.lines;
- default = "";
- description = ''
- Shell commands executed before the service's main process
- is started.
- '';
- };
-
- postStart = mkOption {
- type = types.lines;
- default = "";
- description = ''
- Shell commands executed after the service's main process
- is started.
- '';
- };
-
- reload = mkOption {
- type = types.lines;
- default = "";
- description = ''
- Shell commands executed when the service's main process
- is reloaded.
- '';
- };
-
- preStop = mkOption {
- type = types.lines;
- default = "";
- description = ''
- Shell commands executed to stop the service.
- '';
- };
-
- postStop = mkOption {
- type = types.lines;
- default = "";
- description = ''
- Shell commands executed after the service's main process
- has exited.
- '';
- };
-
- restartIfChanged = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Whether the service should be restarted during a NixOS
- configuration switch if its definition has changed.
- '';
- };
-
- reloadIfChanged = mkOption {
- type = types.bool;
- default = false;
- description = ''
- Whether the service should be reloaded during a NixOS
- configuration switch if its definition has changed. If
- enabled, the value of is
- ignored.
-
- This option should not be used anymore in favor of
- which allows more granular
- control of when a service is reloaded and when a service
- is restarted.
- '';
- };
-
- stopIfChanged = mkOption {
- type = types.bool;
- default = true;
- description = ''
- If set, a changed unit is restarted by calling
- systemctl stop in the old configuration,
- then systemctl start in the new one.
- Otherwise, it is restarted in a single step using
- systemctl restart in the new configuration.
- The latter is less correct because it runs the
- ExecStop commands from the new
- configuration.
- '';
- };
-
- startAt = mkOption {
- type = with types; either str (listOf str);
- default = [];
- example = "Sun 14:00:00";
- description = ''
- Automatically start this unit at the given date/time, which
- must be in the format described in
- systemd.time
- 7. This is equivalent
- to adding a corresponding timer unit with
- set to the value given here.
- '';
- apply = v: if isList v then v else [ v ];
- };
+ config = mkMerge
+ [ (mkIf (config.preStart != "")
+ { serviceConfig.ExecStartPre =
+ [ (makeJobScript "${name}-pre-start" config.preStart) ];
+ })
+ (mkIf (config.script != "")
+ { serviceConfig.ExecStart =
+ makeJobScript "${name}-start" config.script + " " + config.scriptArgs;
+ })
+ (mkIf (config.postStart != "")
+ { serviceConfig.ExecStartPost =
+ [ (makeJobScript "${name}-post-start" config.postStart) ];
+ })
+ (mkIf (config.reload != "")
+ { serviceConfig.ExecReload =
+ makeJobScript "${name}-reload" config.reload;
+ })
+ (mkIf (config.preStop != "")
+ { serviceConfig.ExecStop =
+ makeJobScript "${name}-pre-stop" config.preStop;
+ })
+ (mkIf (config.postStop != "")
+ { serviceConfig.ExecStopPost =
+ makeJobScript "${name}-post-stop" config.postStop;
+ })
+ ];
+ };
+ stage1ServiceOptions = {
+ imports = [
+ stage1CommonUnitOptions
+ serviceOptions
+ ];
};
- socketOptions = commonUnitOptions // {
+ socketOptions = { options = {
listenStreams = mkOption {
default = [];
@@ -424,10 +475,24 @@ in rec {
'';
};
+ }; };
+
+ stage2SocketOptions = {
+ imports = [
+ stage2CommonUnitOptions
+ socketOptions
+ ];
+ };
+
+ stage1SocketOptions = {
+ imports = [
+ stage1CommonUnitOptions
+ socketOptions
+ ];
};
- timerOptions = commonUnitOptions // {
+ timerOptions = { options = {
timerConfig = mkOption {
default = {};
@@ -443,10 +508,24 @@ in rec {
'';
};
+ }; };
+
+ stage2TimerOptions = {
+ imports = [
+ stage2CommonUnitOptions
+ timerOptions
+ ];
+ };
+
+ stage1TimerOptions = {
+ imports = [
+ stage1CommonUnitOptions
+ timerOptions
+ ];
};
- pathOptions = commonUnitOptions // {
+ pathOptions = { options = {
pathConfig = mkOption {
default = {};
@@ -460,10 +539,24 @@ in rec {
'';
};
+ }; };
+
+ stage2PathOptions = {
+ imports = [
+ stage2CommonUnitOptions
+ pathOptions
+ ];
+ };
+
+ stage1PathOptions = {
+ imports = [
+ stage1CommonUnitOptions
+ pathOptions
+ ];
};
- mountOptions = commonUnitOptions // {
+ mountOptions = { options = {
what = mkOption {
example = "/dev/sda1";
@@ -505,9 +598,23 @@ in rec {
5 for details.
'';
};
+ }; };
+
+ stage2MountOptions = {
+ imports = [
+ stage2CommonUnitOptions
+ mountOptions
+ ];
};
- automountOptions = commonUnitOptions // {
+ stage1MountOptions = {
+ imports = [
+ stage1CommonUnitOptions
+ mountOptions
+ ];
+ };
+
+ automountOptions = { options = {
where = mkOption {
example = "/mnt";
@@ -529,11 +636,23 @@ in rec {
5 for details.
'';
};
+ }; };
+
+ stage2AutomountOptions = {
+ imports = [
+ stage2CommonUnitOptions
+ automountOptions
+ ];
};
- targetOptions = commonUnitOptions;
+ stage1AutomountOptions = {
+ imports = [
+ stage1CommonUnitOptions
+ automountOptions
+ ];
+ };
- sliceOptions = commonUnitOptions // {
+ sliceOptions = { options = {
sliceConfig = mkOption {
default = {};
@@ -547,6 +666,20 @@ in rec {
'';
};
+ }; };
+
+ stage2SliceOptions = {
+ imports = [
+ stage2CommonUnitOptions
+ sliceOptions
+ ];
+ };
+
+ stage1SliceOptions = {
+ imports = [
+ stage1CommonUnitOptions
+ sliceOptions
+ ];
};
}
diff --git a/nixos/lib/utils.nix b/nixos/lib/utils.nix
index ae68c3920c5b..80341dd48fcd 100644
--- a/nixos/lib/utils.nix
+++ b/nixos/lib/utils.nix
@@ -197,5 +197,6 @@ rec {
systemdUtils = {
lib = import ./systemd-lib.nix { inherit lib config pkgs; };
unitOptions = import ./systemd-unit-options.nix { inherit lib systemdUtils; };
+ types = import ./systemd-types.nix { inherit lib systemdUtils; };
};
}
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index d825f4beb301..931201ade293 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -15,6 +15,26 @@ let
mapAttrsToList (n: v: ''${n}=${escapeIfNeccessary (toString v)}'') attrs
);
+ osReleaseContents = {
+ NAME = "NixOS";
+ ID = "nixos";
+ VERSION = "${cfg.release} (${cfg.codeName})";
+ VERSION_CODENAME = toLower cfg.codeName;
+ VERSION_ID = cfg.release;
+ BUILD_ID = cfg.version;
+ PRETTY_NAME = "NixOS ${cfg.release} (${cfg.codeName})";
+ LOGO = "nix-snowflake";
+ HOME_URL = "https://nixos.org/";
+ DOCUMENTATION_URL = "https://nixos.org/learn.html";
+ SUPPORT_URL = "https://nixos.org/community.html";
+ BUG_REPORT_URL = "https://github.com/NixOS/nixpkgs/issues";
+ };
+
+ initrdReleaseContents = osReleaseContents // {
+ PRETTY_NAME = "${osReleaseContents.PRETTY_NAME} (Initrd)";
+ };
+ initrdRelease = pkgs.writeText "initrd-release" (attrsToText initrdReleaseContents);
+
in
{
imports = [
@@ -119,20 +139,12 @@ in
DISTRIB_DESCRIPTION = "NixOS ${cfg.release} (${cfg.codeName})";
};
- "os-release".text = attrsToText {
- NAME = "NixOS";
- ID = "nixos";
- VERSION = "${cfg.release} (${cfg.codeName})";
- VERSION_CODENAME = toLower cfg.codeName;
- VERSION_ID = cfg.release;
- BUILD_ID = cfg.version;
- PRETTY_NAME = "NixOS ${cfg.release} (${cfg.codeName})";
- LOGO = "nix-snowflake";
- HOME_URL = "https://nixos.org/";
- DOCUMENTATION_URL = "https://nixos.org/learn.html";
- SUPPORT_URL = "https://nixos.org/community.html";
- BUG_REPORT_URL = "https://github.com/NixOS/nixpkgs/issues";
- };
+ "os-release".text = attrsToText osReleaseContents;
+ };
+
+ boot.initrd.systemd.contents = {
+ "/etc/os-release".source = initrdRelease;
+ "/etc/initrd-release".source = initrdRelease;
};
};
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index c4958c36ea00..789faea91977 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1181,6 +1181,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 8b011d91563f..1bafec30b53d 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -703,8 +703,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 f69c5d3d5a64..844a6793c154 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -11,14 +11,7 @@ let
systemd = cfg.package;
inherit (systemdUtils.lib)
- makeUnit
generateUnits
- makeJobScript
- unitConfig
- serviceConfig
- mountConfig
- automountConfig
- commonUnitText
targetToUnit
serviceToUnit
socketToUnit
@@ -185,13 +178,7 @@ in
systemd.units = mkOption {
description = "Definition of systemd units.";
default = {};
- type = with types; attrsOf (submodule (
- { name, config, ... }:
- { options = concreteUnitOptions;
- config = {
- unit = mkDefault (makeUnit name config);
- };
- }));
+ type = systemdUtils.types.units;
};
systemd.packages = mkOption {
@@ -203,37 +190,37 @@ in
systemd.targets = mkOption {
default = {};
- type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
+ type = systemdUtils.types.targets;
description = "Definition of systemd target units.";
};
systemd.services = mkOption {
default = {};
- type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ]);
+ type = systemdUtils.types.services;
description = "Definition of systemd service units.";
};
systemd.sockets = mkOption {
default = {};
- type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ]);
+ type = systemdUtils.types.sockets;
description = "Definition of systemd socket units.";
};
systemd.timers = mkOption {
default = {};
- type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ]);
+ type = systemdUtils.types.timers;
description = "Definition of systemd timer units.";
};
systemd.paths = mkOption {
default = {};
- type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
+ type = systemdUtils.types.paths;
description = "Definition of systemd path units.";
};
systemd.mounts = mkOption {
default = [];
- type = with types; listOf (submodule [ { options = mountOptions; } unitConfig mountConfig ]);
+ type = systemdUtils.types.mounts;
description = ''
Definition of systemd mount units.
This is a list instead of an attrSet, because systemd mandates the names to be derived from
@@ -243,7 +230,7 @@ in
systemd.automounts = mkOption {
default = [];
- type = with types; listOf (submodule [ { options = automountOptions; } unitConfig automountConfig ]);
+ type = systemdUtils.types.automounts;
description = ''
Definition of systemd automount units.
This is a list instead of an attrSet, because systemd mandates the names to be derived from
@@ -253,7 +240,7 @@ in
systemd.slices = mkOption {
default = {};
- type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig] );
+ type = systemdUtils.types.slices;
description = "Definition of slice configurations.";
};
@@ -362,10 +349,11 @@ in
type = types.listOf types.str;
example = [ "systemd-backlight@.service" ];
description = ''
- A list of units to suppress when generating system systemd configuration directory. This has
+ A list of units to skip 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.
+ prevent a upstream systemd unit from being added to the initrd with any modifications made to it
+ by other NixOS modules.
'';
};
@@ -482,7 +470,12 @@ in
enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedSystemUnits) cfg.units;
in ({
- "systemd/system".source = generateUnits "system" enabledUnits enabledUpstreamSystemUnits upstreamSystemWants;
+ "systemd/system".source = generateUnits {
+ type = "system";
+ units = enabledUnits;
+ upstreamUnits = enabledUpstreamSystemUnits;
+ upstreamWants = upstreamSystemWants;
+ };
"systemd/system.conf".text = ''
[Manager]
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
new file mode 100644
index 000000000000..30bdc9a3422c
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -0,0 +1,417 @@
+{ lib, config, utils, pkgs, ... }:
+
+with lib;
+
+let
+ inherit (utils) systemdUtils escapeSystemdPath;
+ inherit (systemdUtils.lib)
+ generateUnits
+ pathToUnit
+ serviceToUnit
+ 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, autoFormat, autoResize, ... }@fs: let
+ opts = options ++ optional autoFormat "x-systemd.makefs" ++ optional autoResize "x-systemd.growfs";
+ in "${device} /sysroot${mountPoint} ${fsType} ${lib.concatStringsSep "," opts}") 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"];
+ # Make recovery easier
+ postBuild = ''
+ ln -s ${cfg.package.util-linux}/bin/mount $out/bin/
+ ln -s ${cfg.package.util-linux}/bin/umount $out/bin/
+ '';
+ };
+
+ initialRamdisk = pkgs.makeInitrdNG {
+ contents = map (path: { object = path; symlink = ""; }) (subtractLists cfg.suppressedStorePaths cfg.storePaths)
+ ++ mapAttrsToList (_: v: { object = v.source; symlink = v.target; }) (filterAttrs (_: v: v.enable) cfg.contents);
+ };
+
+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 = (mkPackageOption pkgs "systemd" {
+ default = "systemdMinimal";
+ }) // {
+ visible = false;
+ };
+
+ contents = mkOption {
+ description = "Set of files that have to be linked into the initrd";
+ example = literalExpression ''
+ {
+ "/etc/hostname".text = "mymachine";
+ }
+ '';
+ visible = false;
+ default = {};
+ type = types.attrsOf (types.submodule ({ config, options, name, ... }: {
+ options = {
+ enable = mkEnableOption "copying of this file to initrd and symlinking it" // { default = true; };
+
+ target = mkOption {
+ type = types.path;
+ description = ''
+ Path of the symlink.
+ '';
+ default = name;
+ };
+
+ text = mkOption {
+ default = null;
+ type = types.nullOr types.lines;
+ description = "Text of the file.";
+ };
+
+ source = mkOption {
+ type = types.path;
+ description = "Path of the source file.";
+ };
+ };
+
+ config = {
+ source = mkIf (config.text != null) (
+ let name' = "initrd-" + baseNameOf name;
+ in mkDerivedConfig options.text (pkgs.writeText name')
+ );
+ };
+ }));
+ };
+
+ storePaths = mkOption {
+ description = ''
+ Store paths to copy into the initrd as well.
+ '';
+ type = types.listOf types.singleLineStr;
+ default = [];
+ };
+
+ suppressedStorePaths = mkOption {
+ description = ''
+ Store paths specified in the storePaths option that
+ should not be copied.
+ '';
+ type = types.listOf types.singleLineStr;
+ default = [];
+ };
+
+ emergencyAccess = mkOption {
+ type = with types; oneOf [ bool singleLineStr ];
+ visible = false;
+ description = ''
+ Set to true for unauthenticated emergency access, and false for
+ no emergency access.
+
+ Can also be set to a hashed super user password to allow
+ authenticated access to the emergency mode.
+ '';
+ default = false;
+ };
+
+ 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 skip when generating system systemd configuration directory. This has
+ priority over upstream units, , and
+ . The main purpose of this is to
+ prevent a upstream systemd unit from being added to the initrd 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.initrdTargets;
+ 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.initrdSockets;
+ visible = false;
+ description = "Definition of systemd socket units.";
+ };
+
+ timers = mkOption {
+ default = {};
+ type = systemdUtils.types.initrdTimers;
+ visible = false;
+ description = "Definition of systemd timer units.";
+ };
+
+ paths = mkOption {
+ default = {};
+ type = systemdUtils.types.initrdPaths;
+ visible = false;
+ description = "Definition of systemd path units.";
+ };
+
+ mounts = mkOption {
+ default = [];
+ type = systemdUtils.types.initrdMounts;
+ 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] ++ config.system.fsPackages;
+
+ contents = {
+ "/init".source = "${cfg.package}/lib/systemd/systemd";
+ "/etc/systemd/system".source = stage1Units;
+
+ "/etc/systemd/system.conf".text = ''
+ [Manager]
+ DefaultEnvironment=PATH=/bin:/sbin
+ '';
+
+ "/etc/fstab".source = fstab;
+
+ "/lib/modules".source = "${modulesClosure}/lib/modules";
+
+ "/etc/modules-load.d/nixos.conf".text = concatStringsSep "\n" config.boot.initrd.kernelModules;
+
+ "/etc/passwd".source = "${pkgs.fakeNss}/etc/passwd";
+ "/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then "!" else cfg.emergencyAccess}:::::::";
+
+ "/bin".source = "${initrdBinEnv}/bin";
+ "/sbin".source = "${initrdBinEnv}/sbin";
+
+ "/etc/sysctl.d/nixos.conf".text = "kernel.modprobe = /sbin/modprobe";
+ };
+
+ storePaths = [
+ # TODO: Limit this to the bare necessities
+ "${cfg.package}/lib"
+
+ "${cfg.package.util-linux}/bin/mount"
+ "${cfg.package.util-linux}/bin/umount"
+ "${cfg.package.util-linux}/bin/sulogin"
+
+ # so NSS can look up usernames
+ "${pkgs.glibc}/lib/libnss_files.so"
+ ];
+
+ targets.initrd.aliases = ["default.target"];
+ units =
+ mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths
+ // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit 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);
+
+ services.emergency = mkIf (isBool cfg.emergencyAccess && cfg.emergencyAccess) {
+ environment.SYSTEMD_SULOGIN_FORCE = "1";
+ };
+ # The unit in /run/systemd/generator shadows the unit in
+ # /etc/systemd/system, but will still apply drop-ins from
+ # /etc/systemd/system/foo.service.d/
+ #
+ # We need IgnoreOnIsolate, otherwise the Requires dependency of
+ # a mount unit on its makefs unit causes it to be unmounted when
+ # we isolate for switch-root. Use a dummy package so that
+ # generateUnits will generate drop-ins instead of unit files.
+ packages = [(pkgs.runCommand "dummy" {} ''
+ mkdir -p $out/etc/systemd/system
+ touch $out/etc/systemd/system/systemd-{makefs,growfs}@.service
+ '')];
+ services."systemd-makefs@".unitConfig.IgnoreOnIsolate = true;
+ services."systemd-growfs@".unitConfig.IgnoreOnIsolate = true;
+ };
+ };
+}
diff --git a/nixos/modules/system/boot/systemd/nspawn.nix b/nixos/modules/system/boot/systemd/nspawn.nix
index 0c6822319a5b..bf9995d03cc1 100644
--- a/nixos/modules/system/boot/systemd/nspawn.nix
+++ b/nixos/modules/system/boot/systemd/nspawn.nix
@@ -116,7 +116,13 @@ in {
in
mkMerge [
(mkIf (cfg != {}) {
- environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits' false "nspawn" units [] []);
+ environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits {
+ allowCollisions = false;
+ type = "nspawn";
+ inherit units;
+ upstreamUnits = [];
+ upstreamWants = [];
+ });
})
{
systemd.targets.multi-user.wants = [ "machines.target" ];
diff --git a/nixos/modules/system/boot/systemd/user.nix b/nixos/modules/system/boot/systemd/user.nix
index e30f83f3457f..4951aef95584 100644
--- a/nixos/modules/system/boot/systemd/user.nix
+++ b/nixos/modules/system/boot/systemd/user.nix
@@ -12,10 +12,6 @@ let
(systemdUtils.lib)
makeUnit
generateUnits
- makeJobScript
- unitConfig
- serviceConfig
- commonUnitText
targetToUnit
serviceToUnit
socketToUnit
@@ -57,48 +53,42 @@ in {
systemd.user.units = mkOption {
description = "Definition of systemd per-user units.";
default = {};
- type = with types; attrsOf (submodule (
- { name, config, ... }:
- { options = concreteUnitOptions;
- config = {
- unit = mkDefault (makeUnit name config);
- };
- }));
+ type = systemdUtils.types.units;
};
systemd.user.paths = mkOption {
default = {};
- type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
+ type = systemdUtils.types.paths;
description = "Definition of systemd per-user path units.";
};
systemd.user.services = mkOption {
default = {};
- type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ] );
+ type = systemdUtils.types.services;
description = "Definition of systemd per-user service units.";
};
systemd.user.slices = mkOption {
default = {};
- type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig ] );
+ type = systemdUtils.types.slices;
description = "Definition of systemd per-user slice units.";
};
systemd.user.sockets = mkOption {
default = {};
- type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ] );
+ type = systemdUtils.types.sockets;
description = "Definition of systemd per-user socket units.";
};
systemd.user.targets = mkOption {
default = {};
- type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
+ type = systemdUtils.types.targets;
description = "Definition of systemd per-user target units.";
};
systemd.user.timers = mkOption {
default = {};
- type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ] );
+ type = systemdUtils.types.timers;
description = "Definition of systemd per-user timer units.";
};
@@ -119,7 +109,12 @@ in {
];
environment.etc = {
- "systemd/user".source = generateUnits "user" cfg.units upstreamUserUnits [];
+ "systemd/user".source = generateUnits {
+ type = "user";
+ inherit (cfg) units;
+ upstreamUnits = upstreamUserUnits;
+ upstreamWants = [];
+ };
"systemd/user.conf".text = ''
[Manager]
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index d68edd8d7d39..b8afe231dd2e 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -352,7 +352,7 @@ in
unitConfig.DefaultDependencies = false; # needed to prevent a cycle
serviceConfig.Type = "oneshot";
};
- in listToAttrs (map formatDevice (filter (fs: fs.autoFormat) fileSystems)) // {
+ in listToAttrs (map formatDevice (filter (fs: fs.autoFormat && !(utils.fsNeededForBoot fs)) fileSystems)) // {
# Mount /sys/fs/pstore for evacuating panic logs and crashdumps from persistent storage onto the disk using systemd-pstore.
# This cannot be done with the other special filesystems because the pstore module (which creates the mount point) is not loaded then.
"mount-pstore" = {
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index 760f69121612..74f6521462b8 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -923,6 +923,8 @@ in
mkVMOverride (cfg.fileSystems //
{
"/".device = cfg.bootDevice;
+ "/".fsType = "ext4";
+ "/".autoFormat = true;
"/tmp" = mkIf config.boot.tmpOnTmpfs
{ device = "tmpfs";
@@ -953,6 +955,28 @@ in
};
} // lib.mapAttrs' mkSharedDir cfg.sharedDirectories);
+ boot.initrd.systemd = lib.mkIf (config.boot.initrd.systemd.enable && cfg.writableStore) {
+ mounts = [{
+ where = "/sysroot/nix/store";
+ what = "overlay";
+ type = "overlay";
+ options = "lowerdir=/sysroot/nix/.ro-store,upperdir=/sysroot/nix/.rw-store/store,workdir=/sysroot/nix/.rw-store/work";
+ wantedBy = ["local-fs.target"];
+ before = ["local-fs.target"];
+ requires = ["sysroot-nix-.ro\\x2dstore.mount" "sysroot-nix-.rw\\x2dstore.mount" "rw-store.service"];
+ after = ["sysroot-nix-.ro\\x2dstore.mount" "sysroot-nix-.rw\\x2dstore.mount" "rw-store.service"];
+ unitConfig.IgnoreOnIsolate = true;
+ }];
+ services.rw-store = {
+ after = ["sysroot-nix-.rw\\x2dstore.mount"];
+ unitConfig.DefaultDependencies = false;
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "/bin/mkdir -p 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store";
+ };
+ };
+ };
+
swapDevices = mkVMOverride [ ];
boot.initrd.luks.devices = mkVMOverride {};
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index cf4bfecf6f19..dcbdf34e9441 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -514,6 +514,7 @@ in
systemd-confinement = handleTest ./systemd-confinement.nix {};
systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {};
systemd-escaping = handleTest ./systemd-escaping.nix {};
+ systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {};
systemd-journal = handleTest ./systemd-journal.nix {};
systemd-machinectl = handleTest ./systemd-machinectl.nix {};
systemd-networkd = handleTest ./systemd-networkd.nix {};
diff --git a/nixos/tests/systemd-initrd-simple.nix b/nixos/tests/systemd-initrd-simple.nix
new file mode 100644
index 000000000000..ba62cdf3bbc7
--- /dev/null
+++ b/nixos/tests/systemd-initrd-simple.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+ name = "systemd-initrd-simple";
+
+ machine = { pkgs, ... }: {
+ boot.initrd.systemd = {
+ enable = true;
+ emergencyAccess = true;
+ };
+ fileSystems = lib.mkVMOverride {
+ "/".autoResize = true;
+ };
+ };
+
+ testScript = ''
+ import subprocess
+
+ oldAvail = machine.succeed("df --output=avail / | sed 1d")
+ machine.shutdown()
+
+ subprocess.check_call(["qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"])
+
+ machine.start()
+ newAvail = machine.succeed("df --output=avail / | sed 1d")
+
+ assert int(oldAvail) < int(newAvail), "File system did not grow"
+ '';
+})
diff --git a/pkgs/build-support/docker/default.nix b/pkgs/build-support/docker/default.nix
index 5718cadd4ffa..96ea363c811e 100644
--- a/pkgs/build-support/docker/default.nix
+++ b/pkgs/build-support/docker/default.nix
@@ -6,6 +6,7 @@
, coreutils
, e2fsprogs
, fakechroot
+, fakeNss
, fakeroot
, findutils
, go
@@ -747,25 +748,7 @@ rec {
# Useful when packaging binaries that insist on using nss to look up
# username/groups (like nginx).
# /bin/sh is fine to not exist, and provided by another shim.
- fakeNss = symlinkJoin {
- name = "fake-nss";
- paths = [
- (writeTextDir "etc/passwd" ''
- root:x:0:0:root user:/var/empty:/bin/sh
- nobody:x:65534:65534:nobody:/var/empty:/bin/sh
- '')
- (writeTextDir "etc/group" ''
- root:x:0:
- nobody:x:65534:
- '')
- (writeTextDir "etc/nsswitch.conf" ''
- hosts: files dns
- '')
- (runCommand "var-empty" { } ''
- mkdir -p $out/var/empty
- '')
- ];
- };
+ inherit fakeNss; # alias
# This provides a /usr/bin/env, for shell scripts using the
# "#!/usr/bin/env executable" shebang.
diff --git a/pkgs/build-support/fake-nss/default.nix b/pkgs/build-support/fake-nss/default.nix
new file mode 100644
index 000000000000..9e0b60133e00
--- /dev/null
+++ b/pkgs/build-support/fake-nss/default.nix
@@ -0,0 +1,24 @@
+# Provide a /etc/passwd and /etc/group that contain root and nobody.
+# Useful when packaging binaries that insist on using nss to look up
+# username/groups (like nginx).
+# /bin/sh is fine to not exist, and provided by another shim.
+{ symlinkJoin, writeTextDir, runCommand }:
+symlinkJoin {
+ name = "fake-nss";
+ paths = [
+ (writeTextDir "etc/passwd" ''
+ root:x:0:0:root user:/var/empty:/bin/sh
+ nobody:x:65534:65534:nobody:/var/empty:/bin/sh
+ '')
+ (writeTextDir "etc/group" ''
+ root:x:0:
+ nobody:x:65534:
+ '')
+ (writeTextDir "etc/nsswitch.conf" ''
+ hosts: files dns
+ '')
+ (runCommand "var-empty" { } ''
+ mkdir -p $out/var/empty
+ '')
+ ];
+}
diff --git a/pkgs/build-support/kernel/make-initrd-ng-tool.nix b/pkgs/build-support/kernel/make-initrd-ng-tool.nix
new file mode 100644
index 000000000000..66ffc09d43cf
--- /dev/null
+++ b/pkgs/build-support/kernel/make-initrd-ng-tool.nix
@@ -0,0 +1,9 @@
+{ rustPlatform }:
+
+rustPlatform.buildRustPackage {
+ pname = "make-initrd-ng";
+ version = "0.1.0";
+
+ src = ./make-initrd-ng;
+ cargoLock.lockFile = ./make-initrd-ng/Cargo.lock;
+}
diff --git a/pkgs/build-support/kernel/make-initrd-ng.nix b/pkgs/build-support/kernel/make-initrd-ng.nix
new file mode 100644
index 000000000000..9fd202c44847
--- /dev/null
+++ b/pkgs/build-support/kernel/make-initrd-ng.nix
@@ -0,0 +1,79 @@
+let
+ # Some metadata on various compression programs, relevant to naming
+ # the initramfs file and, if applicable, generating a u-boot image
+ # from it.
+ compressors = import ./initrd-compressor-meta.nix;
+ # Get the basename of the actual compression program from the whole
+ # compression command, for the purpose of guessing the u-boot
+ # compression type and filename extension.
+ compressorName = fullCommand: builtins.elemAt (builtins.match "([^ ]*/)?([^ ]+).*" fullCommand) 1;
+in
+{ stdenvNoCC, perl, cpio, ubootTools, lib, pkgsBuildHost, makeInitrdNGTool, patchelf, runCommand, glibc
+# Name of the derivation (not of the resulting file!)
+, name ? "initrd"
+
+# Program used to compress the cpio archive; use "cat" for no compression.
+# This can also be a function which takes a package set and returns the path to the compressor,
+# such as `pkgs: "${pkgs.lzop}/bin/lzop"`.
+, compressor ? "gzip"
+, _compressorFunction ?
+ if lib.isFunction compressor then compressor
+ else if ! builtins.hasContext compressor && builtins.hasAttr compressor compressors then compressors.${compressor}.executable
+ else _: compressor
+, _compressorExecutable ? _compressorFunction pkgsBuildHost
+, _compressorName ? compressorName _compressorExecutable
+, _compressorMeta ? compressors.${_compressorName} or {}
+
+# List of arguments to pass to the compressor program, or null to use its defaults
+, compressorArgs ? null
+, _compressorArgsReal ? if compressorArgs == null then _compressorMeta.defaultArgs or [] else compressorArgs
+
+# Filename extension to use for the compressed initramfs. This is
+# included for clarity, but $out/initrd will always be a symlink to
+# the final image.
+# If this isn't guessed, you may want to complete the metadata above and send a PR :)
+, extension ? _compressorMeta.extension or
+ (throw "Unrecognised compressor ${_compressorName}, please specify filename extension")
+
+# List of { object = path_or_derivation; symlink = "/path"; }
+# The paths are copied into the initramfs in their nix store path
+# form, then linked at the root according to `symlink`.
+, contents
+
+# List of uncompressed cpio files to prepend to the initramfs. This
+# can be used to add files in specified paths without them becoming
+# symlinks to store paths.
+, prepend ? []
+
+# Whether to wrap the initramfs in a u-boot image.
+, makeUInitrd ? stdenvNoCC.hostPlatform.linux-kernel.target == "uImage"
+
+# If generating a u-boot image, the architecture to use. The default
+# guess may not align with u-boot's nomenclature correctly, so it can
+# be overridden.
+# See https://gitlab.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L81-106 for a list.
+, uInitrdArch ? stdenvNoCC.hostPlatform.linuxArch
+
+# The name of the compression, as recognised by u-boot.
+# See https://gitlab.denx.de/u-boot/u-boot/-/blob/9bfb567e5f1bfe7de8eb41f8c6d00f49d2b9a426/common/image.c#L195-204 for a list.
+# If this isn't guessed, you may want to complete the metadata above and send a PR :)
+, uInitrdCompression ? _compressorMeta.ubootName or
+ (throw "Unrecognised compressor ${_compressorName}, please specify uInitrdCompression")
+}: runCommand name {
+ compress = "${_compressorExecutable} ${lib.escapeShellArgs _compressorArgsReal}";
+ passthru = {
+ compressorExecutableFunction = _compressorFunction;
+ compressorArgs = _compressorArgsReal;
+ };
+
+ passAsFile = ["contents"];
+ contents = lib.concatMapStringsSep "\n" ({ object, symlink, ... }: "${object}\n${if symlink == null then "" else symlink}") contents + "\n";
+
+ nativeBuildInputs = [makeInitrdNGTool patchelf glibc cpio];
+} ''
+ mkdir ./root
+ make-initrd-ng "$contentsPath" ./root
+ mkdir "$out"
+ (cd root && find * .[^.*] -exec touch -h -d '@1' '{}' +)
+ (cd root && find * .[^.*] -print0 | sort -z | cpio -o -H newc -R +0:+0 --reproducible --null | eval -- $compress >> "$out/initrd")
+''
diff --git a/pkgs/build-support/kernel/make-initrd-ng/Cargo.lock b/pkgs/build-support/kernel/make-initrd-ng/Cargo.lock
new file mode 100644
index 000000000000..75e732029b51
--- /dev/null
+++ b/pkgs/build-support/kernel/make-initrd-ng/Cargo.lock
@@ -0,0 +1,5 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "make-initrd-ng"
+version = "0.1.0"
diff --git a/pkgs/build-support/kernel/make-initrd-ng/Cargo.toml b/pkgs/build-support/kernel/make-initrd-ng/Cargo.toml
new file mode 100644
index 000000000000..9076f6b15617
--- /dev/null
+++ b/pkgs/build-support/kernel/make-initrd-ng/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "make-initrd-ng"
+version = "0.1.0"
+authors = ["Will Fancher "]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
diff --git a/pkgs/build-support/kernel/make-initrd-ng/README.md b/pkgs/build-support/kernel/make-initrd-ng/README.md
new file mode 100644
index 000000000000..741eba67e43f
--- /dev/null
+++ b/pkgs/build-support/kernel/make-initrd-ng/README.md
@@ -0,0 +1,79 @@
+# What is this for?
+
+NixOS's traditional initrd is generated by listing the paths that
+should be included in initrd and copying the full runtime closure of
+those paths into the archive. For most things, like almost any
+executable, this involves copying the entirety of huge packages like
+glibc, when only things like the shared library files are needed. To
+solve this, NixOS does a variety of patchwork to edit the files being
+copied in so they only refer to small, patched up paths. For instance,
+executables and their shared library dependencies are copied into an
+`extraUtils` derivation, and every ELF file is patched to refer to
+files in that output.
+
+The problem with this is that it is often difficult to correctly patch
+some things. For instance, systemd bakes the path to the `mount`
+command into the binary, so patchelf is no help. Instead, it's very
+often easier to simply copy the desired files to their original store
+locations in initrd and not copy their entire runtime closure. This
+does mean that it is the burden of the developer to ensure that all
+necessary dependencies are copied in, as closures won't be
+consulted. However, it is rare that full closures are actually
+desirable, so in the traditional initrd, the developer was likely to
+do manual work on patching the dependencies explicitly anyway.
+
+# How it works
+
+This program is similar to its inspiration (`find-libs` from the
+traditional initrd), except that it also handles symlinks and
+directories according to certain rules. As input, it receives a
+sequence of pairs of paths. The first path is an object to copy into
+initrd. The second path (if not empty) is the path to a symlink that
+should be placed in the initrd, pointing to that object. How that
+object is copied depends on its type.
+
+1. A regular file is copied directly to the same absolute path in the
+ initrd.
+
+ - If it is *also* an ELF file, then all of its direct shared
+ library dependencies are also listed as objects to be copied.
+
+2. A directory's direct children are listed as objects to be copied,
+ and a directory at the same absolute path in the initrd is created.
+
+3. A symlink's target is listed as an object to be copied.
+
+There are a couple of quirks to mention here. First, the term "object"
+refers to the final file path that the developer intends to have
+copied into initrd. This means any parent directory is not considered
+an object just because its child was listed as an object in the
+program input; instead those intermediate directories are simply
+created in support of the target object. Second, shared libraries,
+directory children, and symlink targets aren't immediately recursed,
+because they simply get listed as objects themselves, and are
+therefore traversed when they themselves are processed. Finally,
+symlinks in the intermediate directories leading to an object are
+preserved, meaning an input object `/a/symlink/b` will just result in
+initrd containing `/a/symlink -> /target/b` and `/target/b`, even if
+`/target` has other children. Preserving symlinks in this manner is
+important for things like systemd.
+
+These rules automate the most important and obviously necessary
+copying that needs to be done in most cases, allowing programs and
+configuration files to go unpatched, while keeping the content of the
+initrd to a minimum.
+
+# Why Rust?
+
+- A prototype of this logic was written in Bash, in an attempt to keep
+ with its `find-libs` ancestor, but that program was difficult to
+ write, and ended up taking several minutes to run. This program runs
+ in less than a second, and the code is substantially easier to work
+ with.
+
+- This will not require end users to install a rust toolchain to use
+ NixOS, as long as this tool is cached by Hydra. And if you're
+ bootstrapping NixOS from source, rustc is already required anyway.
+
+- Rust was favored over Python for its type system, and because if you
+ want to go fast, why not go *really fast*?
diff --git a/pkgs/build-support/kernel/make-initrd-ng/src/main.rs b/pkgs/build-support/kernel/make-initrd-ng/src/main.rs
new file mode 100644
index 000000000000..1342734590f7
--- /dev/null
+++ b/pkgs/build-support/kernel/make-initrd-ng/src/main.rs
@@ -0,0 +1,208 @@
+use std::collections::{HashSet, VecDeque};
+use std::env;
+use std::ffi::OsStr;
+use std::fs;
+use std::hash::Hash;
+use std::io::{BufReader, BufRead, Error, ErrorKind};
+use std::os::unix;
+use std::path::{Component, Path, PathBuf};
+use std::process::{Command, Stdio};
+
+struct NonRepeatingQueue {
+ queue: VecDeque,
+ seen: HashSet,
+}
+
+impl NonRepeatingQueue {
+ fn new() -> NonRepeatingQueue {
+ NonRepeatingQueue {
+ queue: VecDeque::new(),
+ seen: HashSet::new(),
+ }
+ }
+}
+
+impl NonRepeatingQueue {
+ fn push_back(&mut self, value: T) -> bool {
+ if self.seen.contains(&value) {
+ false
+ } else {
+ self.seen.insert(value.clone());
+ self.queue.push_back(value);
+ true
+ }
+ }
+
+ fn pop_front(&mut self) -> Option {
+ self.queue.pop_front()
+ }
+}
+
+fn patch_elf, P: AsRef>(mode: S, path: P) -> Result {
+ let output = Command::new("patchelf")
+ .arg(&mode)
+ .arg(&path)
+ .stderr(Stdio::inherit())
+ .output()?;
+ if output.status.success() {
+ Ok(String::from_utf8(output.stdout).expect("Failed to parse output"))
+ } else {
+ Err(Error::new(ErrorKind::Other, format!("failed: patchelf {:?} {:?}", OsStr::new(&mode), OsStr::new(&path))))
+ }
+}
+
+fn copy_file + AsRef, S: AsRef>(
+ source: P,
+ target: S,
+ queue: &mut NonRepeatingQueue>,
+) -> Result<(), Error> {
+ fs::copy(&source, target)?;
+
+ if !Command::new("ldd").arg(&source).output()?.status.success() {
+ //stdout(Stdio::inherit()).stderr(Stdio::inherit()).
+ println!("{:?} is not dynamically linked. Not recursing.", OsStr::new(&source));
+ return Ok(());
+ }
+
+ let rpath_string = patch_elf("--print-rpath", &source)?;
+ let needed_string = patch_elf("--print-needed", &source)?;
+ // Shared libraries don't have an interpreter
+ if let Ok(interpreter_string) = patch_elf("--print-interpreter", &source) {
+ queue.push_back(Box::from(Path::new(&interpreter_string.trim())));
+ }
+
+ let rpath = rpath_string.trim().split(":").map(|p| Box::::from(Path::new(p))).collect::>();
+
+ for line in needed_string.lines() {
+ let mut found = false;
+ for path in &rpath {
+ let lib = path.join(line);
+ if lib.exists() {
+ // No need to recurse. The queue will bring it back round.
+ queue.push_back(Box::from(lib.as_path()));
+ found = true;
+ break;
+ }
+ }
+ if !found {
+ // glibc makes it tricky to make this an error because
+ // none of the files have a useful rpath.
+ println!("Warning: Couldn't satisfy dependency {} for {:?}", line, OsStr::new(&source));
+ }
+ }
+
+ Ok(())
+}
+
+fn queue_dir>(
+ source: P,
+ queue: &mut NonRepeatingQueue>,
+) -> Result<(), Error> {
+ for entry in fs::read_dir(source)? {
+ let entry = entry?;
+ // No need to recurse. The queue will bring us back round here on its own.
+ queue.push_back(Box::from(entry.path().as_path()));
+ }
+
+ Ok(())
+}
+
+fn handle_path(
+ root: &Path,
+ p: &Path,
+ queue: &mut NonRepeatingQueue>,
+) -> Result<(), Error> {
+ let mut source = PathBuf::new();
+ let mut target = Path::new(root).to_path_buf();
+ let mut iter = p.components().peekable();
+ while let Some(comp) = iter.next() {
+ match comp {
+ Component::Prefix(_) => panic!("This tool is not meant for Windows"),
+ Component::RootDir => {
+ target.clear();
+ target.push(root);
+ source.clear();
+ source.push("/");
+ }
+ Component::CurDir => {}
+ Component::ParentDir => {
+ // Don't over-pop the target if the path has too many ParentDirs
+ if source.pop() {
+ target.pop();
+ }
+ }
+ Component::Normal(name) => {
+ target.push(name);
+ source.push(name);
+ let typ = fs::symlink_metadata(&source)?.file_type();
+ if typ.is_file() && !target.exists() {
+ copy_file(&source, &target, queue)?;
+ } else if typ.is_symlink() {
+ let link_target = fs::read_link(&source)?;
+
+ // Create the link, then push its target to the queue
+ if !target.exists() {
+ unix::fs::symlink(&link_target, &target)?;
+ }
+ source.pop();
+ source.push(link_target);
+ while let Some(c) = iter.next() {
+ source.push(c);
+ }
+ let link_target_path = source.as_path();
+ if link_target_path.exists() {
+ queue.push_back(Box::from(link_target_path));
+ }
+ break;
+ } else if typ.is_dir() {
+ if !target.exists() {
+ fs::create_dir(&target)?;
+ }
+
+ // Only recursively copy if the directory is the target object
+ if iter.peek().is_none() {
+ queue_dir(&source, queue)?;
+ }
+ }
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn main() -> Result<(), Error> {
+ let args: Vec = env::args().collect();
+ let input = fs::File::open(&args[1])?;
+ let output = &args[2];
+ let out_path = Path::new(output);
+
+ let mut queue = NonRepeatingQueue::>::new();
+
+ let mut lines = BufReader::new(input).lines();
+ while let Some(obj) = lines.next() {
+ // Lines should always come in pairs
+ let obj = obj?;
+ let sym = lines.next().unwrap()?;
+
+ let obj_path = Path::new(&obj);
+ queue.push_back(Box::from(obj_path));
+ if !sym.is_empty() {
+ println!("{} -> {}", &sym, &obj);
+ // We don't care about preserving symlink structure here
+ // nearly as much as for the actual objects.
+ let link_string = format!("{}/{}", output, sym);
+ let link_path = Path::new(&link_string);
+ let mut link_parent = link_path.to_path_buf();
+ link_parent.pop();
+ fs::create_dir_all(link_parent)?;
+ unix::fs::symlink(obj_path, link_path)?;
+ }
+ }
+ while let Some(obj) = queue.pop_front() {
+ println!("{:?}", obj);
+ handle_path(out_path, &*obj, &mut queue)?;
+ }
+
+ Ok(())
+}
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;
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index 41002cf3d7e0..801301bb6985 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -451,6 +451,9 @@ with pkgs;
dockerTools = callPackage ../build-support/docker {
writePython3 = buildPackages.writers.writePython3;
};
+
+ fakeNss = callPackage ../build-support/fake-nss { };
+
tarsum = callPackage ../build-support/docker/tarsum.nix { };
snapTools = callPackage ../build-support/snap { };
@@ -747,6 +750,9 @@ with pkgs;
makeInitrd = callPackage ../build-support/kernel/make-initrd.nix; # Args intentionally left out
+ makeInitrdNG = callPackage ../build-support/kernel/make-initrd-ng.nix;
+ makeInitrdNGTool = callPackage ../build-support/kernel/make-initrd-ng-tool.nix {};
+
makeWrapper = makeSetupHook
{ deps = [ dieHook ];
substitutions = {