diff --git a/nixos/modules/system/boot/grow-partition.nix b/nixos/modules/system/boot/grow-partition.nix index a2764187a533..1ce4d5e56233 100644 --- a/nixos/modules/system/boot/grow-partition.nix +++ b/nixos/modules/system/boot/grow-partition.nix @@ -16,29 +16,28 @@ with lib; }; config = mkIf config.boot.growPartition { + assertions = [ + { + assertion = !config.boot.initrd.systemd.repart.enable && !config.systemd.repart.enable; + message = "systemd-repart already grows the root partition and thus you should not use boot.growPartition"; + } + ]; + systemd.services.growpart = { + wantedBy = [ "-.mount" ]; + after = [ "-.mount" ]; + before = [ "systemd-growfs-root.service" ]; + conflicts = [ "shutdown.target" ]; + unitConfig.DefaultDependencies = false; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + TimeoutSec = "infinity"; + # growpart returns 1 if the partition is already grown + SuccessExitStatus = "0 1"; + }; - assertions = [{ - assertion = !config.boot.initrd.systemd.enable; - message = "systemd stage 1 does not support 'boot.growPartition' yet."; - }]; - - boot.initrd.extraUtilsCommands = '' - copy_bin_and_libs ${pkgs.gawk}/bin/gawk - copy_bin_and_libs ${pkgs.gnused}/bin/sed - copy_bin_and_libs ${pkgs.util-linux}/sbin/sfdisk - copy_bin_and_libs ${pkgs.util-linux}/sbin/lsblk - - substitute "${pkgs.cloud-utils.guest}/bin/.growpart-wrapped" "$out/bin/growpart" \ - --replace "${pkgs.bash}/bin/sh" "/bin/sh" \ - --replace "awk" "gawk" \ - --replace "sed" "gnused" - - ln -s sed $out/bin/gnused - ''; - - boot.initrd.postDeviceCommands = '' - rootDevice="${config.fileSystems."/".device}" - if waitDevice "$rootDevice"; then + script = '' + rootDevice="${config.fileSystems."/".device}" rootDevice="$(readlink -f "$rootDevice")" parentDevice="$rootDevice" while [ "''${parentDevice%[0-9]}" != "''${parentDevice}" ]; do @@ -48,11 +47,8 @@ with lib; if [ "''${parentDevice%[0-9]p}" != "''${parentDevice}" ] && [ -b "''${parentDevice%p}" ]; then parentDevice="''${parentDevice%p}" fi - TMPDIR=/run sh $(type -P growpart) "$parentDevice" "$partNum" - udevadm settle - fi - ''; - + "${pkgs.cloud-utils.guest}/bin/growpart" "$parentDevice" "$partNum" + ''; + }; }; - } diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 22371c9fec37..33f8abf6ccd4 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -332,6 +332,7 @@ in { graphite = handleTest ./graphite.nix {}; graylog = handleTest ./graylog.nix {}; grocy = handleTest ./grocy.nix {}; + grow-partition = runTest ./grow-partition.nix; grub = handleTest ./grub.nix {}; guacamole-server = handleTest ./guacamole-server.nix {}; gvisor = handleTest ./gvisor.nix {}; diff --git a/nixos/tests/grow-partition.nix b/nixos/tests/grow-partition.nix new file mode 100644 index 000000000000..344910848dca --- /dev/null +++ b/nixos/tests/grow-partition.nix @@ -0,0 +1,83 @@ +{ lib, ... }: + +let + rootFslabel = "external"; + rootFsDevice = "/dev/disk/by-label/${rootFslabel}"; + + externalModule = partitionTableType: { config, lib, pkgs, ... }: { + virtualisation.directBoot.enable = false; + virtualisation.mountHostNixStore = false; + virtualisation.useEFIBoot = partitionTableType == "efi"; + + # This stops the qemu-vm module from overriding the fileSystems option + # with virtualisation.fileSystems. + virtualisation.fileSystems = lib.mkForce { }; + + + boot.loader.grub.enable = true; + boot.loader.grub.efiSupport = partitionTableType == "efi"; + boot.loader.grub.efiInstallAsRemovable = partitionTableType == "efi"; + boot.loader.grub.device = if partitionTableType == "efi" then "nodev" else "/dev/vda"; + + boot.growPartition = true; + + fileSystems = { + "/".device = rootFsDevice; + }; + + system.build.diskImage = import ../lib/make-disk-image.nix { + inherit config lib pkgs; + label = rootFslabel; + inherit partitionTableType; + format = "raw"; + bootSize = "128M"; + additionalSpace = "0M"; + copyChannel = false; + }; + }; +in +{ + name = "grow-partition"; + + meta.maintainers = with lib.maintainers; [ arianvp ]; + + nodes = { + efi = externalModule "efi"; + legacy = externalModule "legacy"; + legacyGPT = externalModule "legacy+gpt"; + hybrid = externalModule "hybrid"; + }; + + + testScript = { nodes, ... }: + lib.concatLines (lib.mapAttrsToList (name: node: '' + import os + import subprocess + import tempfile + import shutil + + tmp_disk_image = tempfile.NamedTemporaryFile() + + shutil.copyfile("${node.system.build.diskImage}/nixos.img", tmp_disk_image.name) + + subprocess.run([ + "${node.virtualisation.qemu.package}/bin/qemu-img", + "resize", + "-f", + "raw", + tmp_disk_image.name, + "+32M", + ]) + + # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image. + os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name + + ${name}.wait_for_unit("growpart.service") + systemd_growpart_logs = ${name}.succeed("journalctl --boot --unit growpart.service") + assert "CHANGED" in systemd_growpart_logs + ${name}.succeed("systemctl restart growpart.service") + systemd_growpart_logs = ${name}.succeed("journalctl --boot --unit growpart.service") + assert "NOCHANGE" in systemd_growpart_logs + + '') nodes); +}