nixos/etc-overlay: avoid rebuilding the initrd every time the etc contents change (#340722)
This commit is contained in:
@@ -20,6 +20,12 @@ let
|
||||
''}
|
||||
|
||||
ln -s ${config.system.build.etc}/etc $out/etc
|
||||
|
||||
${lib.optionalString config.system.etc.overlay.enable ''
|
||||
ln -s ${config.system.build.etcMetadataImage} $out/etc-metadata-image
|
||||
ln -s ${config.system.build.etcBasedir} $out/etc-basedir
|
||||
''}
|
||||
|
||||
ln -s ${config.system.path} $out/sw
|
||||
ln -s "$systemd" $out/systemd
|
||||
|
||||
|
||||
@@ -507,12 +507,20 @@ in {
|
||||
in nameValuePair "${n}.automount" (automountToUnit v)) cfg.automounts);
|
||||
|
||||
|
||||
services.initrd-nixos-activation = {
|
||||
after = [ "initrd-fs.target" ];
|
||||
services.initrd-find-nixos-closure = {
|
||||
description = "Find NixOS closure";
|
||||
|
||||
unitConfig = {
|
||||
RequiresMountsFor = "/sysroot/nix/store";
|
||||
DefaultDependencies = false;
|
||||
};
|
||||
before = [ "shutdown.target" ];
|
||||
conflicts = [ "shutdown.target" ];
|
||||
requiredBy = [ "initrd.target" ];
|
||||
unitConfig.AssertPathExists = "/etc/initrd-release";
|
||||
serviceConfig.Type = "oneshot";
|
||||
description = "NixOS Activation";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
|
||||
script = /* bash */ ''
|
||||
set -uo pipefail
|
||||
@@ -542,6 +550,8 @@ in {
|
||||
# Assume the directory containing the init script is the closure.
|
||||
closure="$(dirname "$closure")"
|
||||
|
||||
ln --symbolic "$closure" /nixos-closure
|
||||
|
||||
# If we are not booting a NixOS closure (e.g. init=/bin/sh),
|
||||
# we don't know what root to prepare so we don't do anything
|
||||
if ! [ -x "/sysroot$(readlink "/sysroot$closure/prepare-root" || echo "$closure/prepare-root")" ]; then
|
||||
@@ -550,12 +560,48 @@ in {
|
||||
exit 0
|
||||
fi
|
||||
echo 'NEW_INIT=' > /etc/switch-root.conf
|
||||
'';
|
||||
};
|
||||
|
||||
# We need to propagate /run for things like /run/booted-system
|
||||
# and /run/current-system.
|
||||
mounts = [
|
||||
{
|
||||
where = "/sysroot/run";
|
||||
what = "/run";
|
||||
options = "bind";
|
||||
unitConfig = {
|
||||
# See the comment on the mount unit for /run/etc-metadata
|
||||
DefaultDependencies = false;
|
||||
};
|
||||
requiredBy = [ "initrd-fs.target" ];
|
||||
before = [ "initrd-fs.target" ];
|
||||
}
|
||||
];
|
||||
|
||||
# We need to propagate /run for things like /run/booted-system
|
||||
# and /run/current-system.
|
||||
mkdir -p /sysroot/run
|
||||
mount --bind /run /sysroot/run
|
||||
services.initrd-nixos-activation = {
|
||||
requires = [
|
||||
config.boot.initrd.systemd.services.initrd-find-nixos-closure.name
|
||||
];
|
||||
after = [
|
||||
"initrd-fs.target"
|
||||
config.boot.initrd.systemd.services.initrd-find-nixos-closure.name
|
||||
];
|
||||
requiredBy = [ "initrd.target" ];
|
||||
unitConfig = {
|
||||
AssertPathExists = "/etc/initrd-release";
|
||||
RequiresMountsFor = [
|
||||
"/sysroot/run"
|
||||
];
|
||||
};
|
||||
serviceConfig.Type = "oneshot";
|
||||
description = "NixOS Activation";
|
||||
|
||||
script = /* bash */ ''
|
||||
set -uo pipefail
|
||||
export PATH="/bin:${cfg.package.util-linux}/bin"
|
||||
|
||||
closure="$(realpath /nixos-closure)"
|
||||
|
||||
# Initialize the system
|
||||
export IN_NIXOS_SYSTEMD_STAGE1=true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ config, lib, ... }:
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
{
|
||||
|
||||
@@ -34,12 +34,30 @@
|
||||
mounts = [
|
||||
{
|
||||
where = "/run/etc-metadata";
|
||||
what = "/sysroot${config.system.build.etcMetadataImage}";
|
||||
what = "/etc-metadata-image";
|
||||
type = "erofs";
|
||||
options = "loop";
|
||||
unitConfig.RequiresMountsFor = [
|
||||
"/sysroot/nix/store"
|
||||
unitConfig = {
|
||||
# Since this unit depends on the nix store being mounted, it cannot
|
||||
# be a dependency of local-fs.target, because if it did, we'd have
|
||||
# local-fs.target ordered after the nix store mount which would cause
|
||||
# things like network.target to only become active after the nix store
|
||||
# has been mounted.
|
||||
# This breaks for instance setups where sshd needs to be up before
|
||||
# any encrypted disks can be mounted.
|
||||
DefaultDependencies = false;
|
||||
RequiresMountsFor = [
|
||||
"/sysroot/nix/store"
|
||||
];
|
||||
};
|
||||
requires = [
|
||||
config.boot.initrd.systemd.services.initrd-find-etc.name
|
||||
];
|
||||
after = [
|
||||
config.boot.initrd.systemd.services.initrd-find-etc.name
|
||||
];
|
||||
requiredBy = [ "initrd-fs.target" ];
|
||||
before = [ "initrd-fs.target" ];
|
||||
}
|
||||
{
|
||||
where = "/sysroot/etc";
|
||||
@@ -49,7 +67,7 @@
|
||||
"relatime"
|
||||
"redirect_dir=on"
|
||||
"metacopy=on"
|
||||
"lowerdir=/run/etc-metadata::/sysroot${config.system.build.etcBasedir}"
|
||||
"lowerdir=/run/etc-metadata::/etc-basedir"
|
||||
] ++ lib.optionals config.system.etc.overlay.mutable [
|
||||
"rw"
|
||||
"upperdir=/sysroot/.rw-etc/upper"
|
||||
@@ -59,28 +77,77 @@
|
||||
]);
|
||||
requiredBy = [ "initrd-fs.target" ];
|
||||
before = [ "initrd-fs.target" ];
|
||||
requires = lib.mkIf config.system.etc.overlay.mutable [ "rw-etc.service" ];
|
||||
after = lib.mkIf config.system.etc.overlay.mutable [ "rw-etc.service" ];
|
||||
unitConfig.RequiresMountsFor = [
|
||||
"/sysroot/nix/store"
|
||||
"/run/etc-metadata"
|
||||
requires = [
|
||||
config.boot.initrd.systemd.services.initrd-find-etc.name
|
||||
] ++ lib.optionals config.system.etc.overlay.mutable [
|
||||
config.boot.initrd.systemd.services."rw-etc".name
|
||||
];
|
||||
after = [
|
||||
config.boot.initrd.systemd.services.initrd-find-etc.name
|
||||
] ++ lib.optionals config.system.etc.overlay.mutable [
|
||||
config.boot.initrd.systemd.services."rw-etc".name
|
||||
];
|
||||
unitConfig = {
|
||||
RequiresMountsFor = [
|
||||
"/sysroot/nix/store"
|
||||
"/run/etc-metadata"
|
||||
];
|
||||
DefaultDependencies = false;
|
||||
};
|
||||
}
|
||||
];
|
||||
services = lib.mkIf config.system.etc.overlay.mutable {
|
||||
rw-etc = {
|
||||
unitConfig = {
|
||||
DefaultDependencies = false;
|
||||
RequiresMountsFor = "/sysroot";
|
||||
services = lib.mkMerge [
|
||||
(lib.mkIf config.system.etc.overlay.mutable {
|
||||
rw-etc = {
|
||||
requiredBy = [ "initrd-fs.target" ];
|
||||
before = [ "initrd-fs.target" ];
|
||||
unitConfig = {
|
||||
DefaultDependencies = false;
|
||||
RequiresMountsFor = "/sysroot";
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = ''
|
||||
/bin/mkdir -p -m 0755 /sysroot/.rw-etc/upper /sysroot/.rw-etc/work
|
||||
'';
|
||||
};
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = ''
|
||||
/bin/mkdir -p -m 0755 /sysroot/.rw-etc/upper /sysroot/.rw-etc/work
|
||||
})
|
||||
{
|
||||
initrd-find-etc = {
|
||||
description = "Find the path to the etc metadata image and based dir";
|
||||
requires = [
|
||||
config.boot.initrd.systemd.services.initrd-find-nixos-closure.name
|
||||
];
|
||||
after = [
|
||||
config.boot.initrd.systemd.services.initrd-find-nixos-closure.name
|
||||
];
|
||||
before = [ "shutdown.target" ];
|
||||
conflicts = [ "shutdown.target" ];
|
||||
requiredBy = [ "initrd.target" ];
|
||||
unitConfig = {
|
||||
DefaultDependencies = false;
|
||||
RequiresMountsFor = "/sysroot/nix/store";
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
|
||||
script = /* bash */ ''
|
||||
set -uo pipefail
|
||||
|
||||
closure="$(realpath /nixos-closure)"
|
||||
|
||||
metadata_image="$(chroot /sysroot ${lib.getExe' pkgs.coreutils "realpath"} "$closure/etc-metadata-image")"
|
||||
ln -s "/sysroot$metadata_image" /etc-metadata-image
|
||||
|
||||
basedir="$(chroot /sysroot ${lib.getExe' pkgs.coreutils "realpath"} "$closure/etc-basedir")"
|
||||
ln -s "/sysroot$basedir" /etc-basedir
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
})
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
boot.kernelPackages = pkgs.linuxPackages_latest;
|
||||
time.timeZone = "Utc";
|
||||
|
||||
# The standard resolvconf service tries to write to /etc and crashes,
|
||||
# which makes nixos-rebuild exit uncleanly when switching into the new generation
|
||||
services.resolved.enable = true;
|
||||
|
||||
environment.etc = {
|
||||
"mountpoint/.keep".text = "keep";
|
||||
"filemount".text = "keep";
|
||||
@@ -26,6 +30,13 @@
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
with subtest("/run/etc-metadata/ is mounted"):
|
||||
print(machine.succeed("mountpoint /run/etc-metadata"))
|
||||
|
||||
with subtest("No temporary files leaked into stage 2"):
|
||||
machine.succeed("[ ! -e /etc-metadata-image ]")
|
||||
machine.succeed("[ ! -e /etc-basedir ]")
|
||||
|
||||
with subtest("/etc is mounted as an overlay"):
|
||||
machine.succeed("findmnt --kernel --type overlay /etc")
|
||||
|
||||
@@ -50,6 +61,9 @@
|
||||
with subtest("switching to the same generation"):
|
||||
machine.succeed("/run/current-system/bin/switch-to-configuration test")
|
||||
|
||||
with subtest("the initrd didn't get rebuilt"):
|
||||
machine.succeed("test /run/current-system/initrd -ef /run/current-system/specialisation/new-generation/initrd")
|
||||
|
||||
with subtest("switching to a new generation"):
|
||||
machine.fail("stat /etc/newgen")
|
||||
|
||||
|
||||
@@ -18,12 +18,22 @@
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
with subtest("/run/etc-metadata/ is mounted"):
|
||||
print(machine.succeed("mountpoint /run/etc-metadata"))
|
||||
|
||||
with subtest("No temporary files leaked into stage 2"):
|
||||
machine.succeed("[ ! -e /etc-metadata-image ]")
|
||||
machine.succeed("[ ! -e /etc-basedir ]")
|
||||
|
||||
with subtest("/etc is mounted as an overlay"):
|
||||
machine.succeed("findmnt --kernel --type overlay /etc")
|
||||
|
||||
with subtest("switching to the same generation"):
|
||||
machine.succeed("/run/current-system/bin/switch-to-configuration test")
|
||||
|
||||
with subtest("the initrd didn't get rebuilt"):
|
||||
machine.succeed("test /run/current-system/initrd -ef /run/current-system/specialisation/new-generation/initrd")
|
||||
|
||||
with subtest("switching to a new generation"):
|
||||
machine.fail("stat /etc/newgen")
|
||||
machine.succeed("echo -n 'mutable' > /etc/mutable")
|
||||
|
||||
@@ -29,6 +29,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
|
||||
machine.succeed("[ -e /dev/shm ]") # /dev/shm
|
||||
machine.succeed("[ -e /dev/pts/ptmx ]") # /dev/pts
|
||||
machine.succeed("[ -e /run/keys ]") # /run/keys
|
||||
# /nixos-closure didn't leak into stage-2
|
||||
machine.succeed("[ ! -e /nixos-closure ]")
|
||||
|
||||
with subtest("groups work"):
|
||||
machine.fail("journalctl -b 0 | grep 'systemd-udevd.*Unknown group.*ignoring'")
|
||||
|
||||
Reference in New Issue
Block a user