Unverified Commit 5b23d0e5 authored by Arian van Putten's avatar Arian van Putten Committed by GitHub
Browse files

Merge pull request #214396 from nikstur/systemd-repart

systemd-repart
parents c34c2351 47001986
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1307,6 +1307,7 @@
  ./system/boot/systemd/logind.nix
  ./system/boot/systemd/nspawn.nix
  ./system/boot/systemd/oomd.nix
  ./system/boot/systemd/repart.nix
  ./system/boot/systemd/shutdown.nix
  ./system/boot/systemd/tmpfiles.nix
  ./system/boot/systemd/user.nix
+101 −0
Original line number Diff line number Diff line
{ config, pkgs, lib, ... }:

let
  cfg = config.boot.initrd.systemd.repart;

  writeDefinition = name: partitionConfig: pkgs.writeText
    "${name}.conf"
    (lib.generators.toINI { } { Partition = partitionConfig; });

  listOfDefinitions = lib.mapAttrsToList
    writeDefinition
    (lib.filterAttrs (k: _: !(lib.hasPrefix "_" k)) cfg.partitions);

  # Create a directory in the store that contains a copy of all definition
  # files. This is then passed to systemd-repart in the initrd so it can access
  # the definition files after the sysroot has been mounted but before
  # activation. This needs a hard copy of the files and not just symlinks
  # because otherwise the files do not show up in the sysroot.
  definitionsDirectory = pkgs.runCommand "systemd-repart-definitions" { } ''
    mkdir -p $out
    ${(lib.concatStringsSep "\n"
      (map (pkg: "cp ${pkg} $out/${pkg.name}") listOfDefinitions)
    )}
  '';
in
{
  options.boot.initrd.systemd.repart = {
    enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // {
      description = lib.mdDoc ''
        Grow and add partitions to a partition table a boot time in the initrd.
        systemd-repart only works with GPT partition tables.
      '';
    };

    partitions = lib.mkOption {
      type = with lib.types; attrsOf (attrsOf (oneOf [ str int bool ]));
      default = { };
      example = {
        "10-root" = {
          Type = "root";
        };
        "20-home" = {
          Type = "home";
          SizeMinBytes = "512M";
          SizeMaxBytes = "2G";
        };
      };
      description = lib.mdDoc ''
        Specify partitions as a set of the names of the definition files as the
        key and the partition configuration as its value. The partition
        configuration can use all upstream options. See <link
        xlink:href="https://www.freedesktop.org/software/systemd/man/repart.d.html"/>
        for all available options.
      '';
    };
  };

  config = lib.mkIf cfg.enable {
    # Link the definitions into /etc so that they are included in the
    # /nix/store of the sysroot. This also allows the user to run the
    # systemd-repart binary after activation manually while automatically
    # picking up the definition files.
    environment.etc."repart.d".source = definitionsDirectory;

    boot.initrd.systemd = {
      additionalUpstreamUnits = [
        "systemd-repart.service"
      ];

      storePaths = [
        "${config.boot.initrd.systemd.package}/bin/systemd-repart"
      ];

      # Override defaults in upstream unit.
      services.systemd-repart = {
        # Unset the coniditions as they cannot be met before activation because
        # the definition files are not stored in the expected locations.
        unitConfig.ConditionDirectoryNotEmpty = [
          " " # required to unset the previous value.
        ];
        serviceConfig = {
          # systemd-repart runs before the activation script. Thus we cannot
          # rely on them being linked in /etc already. Instead we have to
          # explicitly pass their location in the sysroot to the binary.
          ExecStart = [
            " " # required to unset the previous value.
            ''${config.boot.initrd.systemd.package}/bin/systemd-repart \
                  --definitions=/sysroot${definitionsDirectory} \
                  --dry-run=no
            ''
          ];
        };
        # Because the initrd does not have the `initrd-usr-fs.target` the
        # upestream unit runs too early in the boot process, before the sysroot
        # is available. However, systemd-repart needs access to the sysroot to
        # find the definition files.
        after = [ "sysroot.mount" ];
      };
    };
  };
}
+1 −0
Original line number Diff line number Diff line
@@ -666,6 +666,7 @@ in {
  systemd-nspawn = handleTest ./systemd-nspawn.nix {};
  systemd-oomd = handleTest ./systemd-oomd.nix {};
  systemd-portabled = handleTest ./systemd-portabled.nix {};
  systemd-repart = handleTest ./systemd-repart.nix {};
  systemd-shutdown = handleTest ./systemd-shutdown.nix {};
  systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
  systemd-user-tmpfiles-rules = handleTest ./systemd-user-tmpfiles-rules.nix {};
+108 −0
Original line number Diff line number Diff line
{ system ? builtins.currentSystem
, config ? { }
, pkgs ? import ../.. { inherit system config; }
}:

with import ../lib/testing-python.nix { inherit system pkgs; };
with pkgs.lib;

let
  # A testScript fragment that prepares a disk with some empty, unpartitioned
  # space. and uses it to boot the test with. Takes a single argument `machine`
  # from which the diskImage is extraced.
  useDiskImage = machine: ''
    import os
    import shutil
    import subprocess
    import tempfile

    tmp_disk_image = tempfile.NamedTemporaryFile()

    shutil.copyfile("${machine.system.build.diskImage}/nixos.img", tmp_disk_image.name)

    subprocess.run([
      "${pkgs.qemu}/bin/qemu-img",
      "resize",
      "-f",
      "raw",
      tmp_disk_image.name,
      "+32M",
    ])

    # Fix the GPT table by moving the backup table to the end of the enlarged
    # disk image. This is necessary because we increased the size of the disk
    # before. The disk needs to be a raw disk because sgdisk can only run on
    # raw images.
    subprocess.run([
      "${pkgs.gptfdisk}/bin/sgdisk",
      "--move-second-header",
      tmp_disk_image.name,
    ])

    # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
    os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
  '';

  common = { config, pkgs, lib, ... }: {
    virtualisation.useDefaultFilesystems = false;
    virtualisation.fileSystems = {
      "/" = {
        device = "/dev/vda2";
        fsType = "ext4";
      };
    };

    boot.initrd.systemd.enable = true;
    boot.initrd.systemd.repart.enable = true;

    # systemd-repart operates on disks with a partition table. The qemu module,
    # however, creates separate filesystem images without a partition table, so
    # we have to create a disk image manually.
    #
    # This creates two partitions, an ESP mounted on /dev/vda1 and the root
    # partition mounted on /dev/vda2
    system.build.diskImage = import ../lib/make-disk-image.nix {
      inherit config pkgs lib;
      # Use a raw format disk so that it can be resized before starting the
      # test VM.
      format = "raw";
      # Keep the image as small as possible but leave some room for changes.
      bootSize = "32M";
      additionalSpace = "0M";
      # GPT with an EFI System Partition is the typical use case for
      # systemd-repart because it does not support MBR.
      partitionTableType = "efi";
      # We do not actually care much about the content of the partitions, so we
      # do not need a bootloader installed.
      installBootLoader = false;
      # Improve determinism by not copying a channel.
      copyChannel = false;
    };
  };
in
{
  basic = makeTest {
    name = "systemd-repart";
    meta.maintainers = with maintainers; [ nikstur ];

    nodes.machine = { config, pkgs, ... }: {
      imports = [ common ];

      boot.initrd.systemd.repart.partitions = {
        "10-root" = {
          Type = "linux-generic";
        };
      };
    };

    testScript = { nodes, ... }: ''
      ${useDiskImage nodes.machine}

      machine.start()
      machine.wait_for_unit("multi-user.target")

      systemd_repart_logs = machine.succeed("journalctl --boot --unit systemd-repart.service")
      assert "Growing existing partition 1." in systemd_repart_logs
    '';
  };
}