nixos/etc-overlay: avoid rebuilding the initrd every time the etc contents change (#340722)

This commit is contained in:
Ramses
2024-10-16 22:21:13 +02:00
committed by GitHub
6 changed files with 175 additions and 30 deletions
@@ -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
+55 -9
View File
@@ -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
+88 -21
View File
@@ -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")
+2
View File
@@ -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'")