Unverified Commit 77152405 authored by Ramses's avatar Ramses Committed by GitHub
Browse files

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

parents 456e8ee0 a4f7868e
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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
Original line number Diff line number Diff line
@@ -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.
          mkdir -p /sysroot/run
          mount --bind /run /sysroot/run
      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" ];
        }
      ];

      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
Original line number Diff line number Diff line
{ 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 = [
            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,16 +77,30 @@
            ]);
            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 = [
            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 {
        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";
@@ -80,7 +112,42 @@
                '';
              };
            };
          })
          {
            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
              '';
            };
          }
        ];
      };

    })
+14 −0
Original line number Diff line number Diff line
@@ -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")

+10 −0
Original line number Diff line number Diff line
@@ -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")
Loading