Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions nixos/modules/system/activation/top-level.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ 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.path} $out/sw
ln -s "$systemd" $out/systemd

Expand Down
89 changes: 73 additions & 16 deletions nixos/modules/system/etc/etc-activation.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{ config, lib, ... }:
{ config, lib, pkgs, ... }:

{

Expand Down Expand Up @@ -34,12 +34,23 @@
mounts = [
{
where = "/run/etc-metadata";
what = "/sysroot${config.system.build.etcMetadataImage}";
what = "/tmp/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 depend
# on 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 = [ "find-etc-metadata-image.service" ];
after = [ "local-fs-pre.target" "find-etc-metadata-image.service" ];
}
{
where = "/sysroot/etc";
Expand Down Expand Up @@ -67,20 +78,66 @@
];
}
];
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 = {
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
})
{
find-etc-metadata-image = {
description = "Find the path to the etc metadata image";
before = [ "shutdown.target" ];
conflicts = [ "shutdown.target" ];
unitConfig = {
DefaultDependencies = false;
RequiresMountsFor = "/sysroot/nix/store";
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};

script = /* bash */ ''
set -uo pipefail

# Figure out what closure to boot
closure=
for o in $(< /proc/cmdline); do
case $o in
init=*)
IFS='=' read -r -a initParam <<< "$o"
closure="''${initParam[1]}"
;;
esac
done

# Sanity check
if [ -z "''${closure:-}" ]; then
echo 'No init= parameter on the kernel command line' >&2
exit 1
fi

# Resolve symlinks in the init parameter
closure="$(chroot /sysroot ${lib.getExe' pkgs.coreutils "realpath"} "$closure")"
# Assume the directory containing the init script is the closure.
closure="$(dirname "$closure")"
Comment on lines +111 to +133
Copy link
Contributor Author

@r-vdp r-vdp Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is basically the same exact logic as what we have for the activation service, where we find the path to prepare-root in the same way.
Maybe it would make sense to create a common service that does this once and makes the closure available in the initrd so that other units can use it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ElvishJerricco I'd like to hear your thoughts on this. Would it make sense to pull this into a separate service that creates a link to the closure so that we can reuse this elsewhere?


metadata_image="$(chroot /sysroot ${lib.getExe' pkgs.coreutils "realpath"} "$closure/etc-metadata-image")"
ln -s "/sysroot$metadata_image" /tmp/etc-metadata-image
'';
};
};
};
}
];
};

})
Expand Down
11 changes: 10 additions & 1 deletion nixos/tests/activation/etc-overlay-immutable.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

meta.maintainers = with lib.maintainers; [ nikstur ];

nodes.machine = { pkgs, ... }: {
nodes.machine = { lib, config, pkgs, ... }: {
system.etc.overlay.enable = true;
system.etc.overlay.mutable = false;

Expand All @@ -23,6 +23,15 @@
specialisation.new-generation.configuration = {
environment.etc."newgen".text = "newgen";
};

assertions = lib.mkIf (lib.length (lib.attrNames config.specialisation) > 0) [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mkIf is needed because otherwise this assertion is also added to the specialisations, which leads to an error because they don't have any specialisations themselves.

{
assertion =
config.system.build.initialRamdisk.drvPath ==
config.specialisation.new-generation.configuration.system.build.initialRamdisk.drvPath;
message = "The initrd for the base system and the specialisation are not the same!";
}
];
};

testScript = ''
Expand Down
11 changes: 10 additions & 1 deletion nixos/tests/activation/etc-overlay-mutable.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

meta.maintainers = with lib.maintainers; [ nikstur ];

nodes.machine = { pkgs, ... }: {
nodes.machine = { lib, config, pkgs, ... }: {
system.etc.overlay.enable = true;
system.etc.overlay.mutable = true;

Expand All @@ -15,6 +15,15 @@
specialisation.new-generation.configuration = {
environment.etc."newgen".text = "newgen";
};

assertions = lib.mkIf (lib.length (lib.attrNames config.specialisation) > 0) [
{
assertion =
config.system.build.initialRamdisk.drvPath ==
config.specialisation.new-generation.configuration.system.build.initialRamdisk.drvPath;
message = "The initrd for the base system and the specialisation are not the same!";
}
];
};

testScript = ''
Expand Down