diff --git a/nixos/modules/system/boot/systemd/repart.nix b/nixos/modules/system/boot/systemd/repart.nix index 1f176252dc1e..c022b167ea12 100644 --- a/nixos/modules/system/boot/systemd/repart.nix +++ b/nixos/modules/system/boot/systemd/repart.nix @@ -1,4 +1,4 @@ -{ config, pkgs, lib, ... }: +{ config, pkgs, lib, utils, ... }: let cfg = config.systemd.repart; @@ -26,14 +26,29 @@ let in { options = { - boot.initrd.systemd.repart.enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // { - description = lib.mdDoc '' - Grow and add partitions to a partition table at boot time in the initrd. - systemd-repart only works with GPT partition tables. + boot.initrd.systemd.repart = { + enable = lib.mkEnableOption (lib.mdDoc "systemd-repart") // { + description = lib.mdDoc '' + Grow and add partitions to a partition table at boot time in the initrd. + systemd-repart only works with GPT partition tables. - To run systemd-repart after the initrd, see - `options.systemd.repart.enable`. - ''; + To run systemd-repart after the initrd, see + `options.systemd.repart.enable`. + ''; + }; + + device = lib.mkOption { + type = with lib.types; nullOr str; + description = lib.mdDoc '' + The device to operate on. + + If `device == null`, systemd-repart will operate on the device + backing the root partition. So in order to dynamically *create* the + root partition in the initrd you need to set a device. + ''; + default = null; + example = "/dev/vda"; + }; }; systemd.repart = { @@ -84,31 +99,42 @@ in contents."/etc/repart.d".source = definitionsDirectory; # Override defaults in upstream unit. - services.systemd-repart = { - # systemd-repart tries to create directories in /var/tmp by default to - # store large temporary files that benefit from persistence on disk. In - # the initrd, however, /var/tmp does not provide more persistence than - # /tmp, so we re-use it here. - environment."TMPDIR" = "/tmp"; - serviceConfig = { - ExecStart = [ - " " # required to unset the previous value. - # When running in the initrd, systemd-repart by default searches - # for definition files in /sysroot or /sysusr. We tell it to look - # in the initrd itself. - ''${config.boot.initrd.systemd.package}/bin/systemd-repart \ + services.systemd-repart = + let + deviceUnit = "${utils.escapeSystemdPath initrdCfg.device}.device"; + in + { + # systemd-repart tries to create directories in /var/tmp by default to + # store large temporary files that benefit from persistence on disk. In + # the initrd, however, /var/tmp does not provide more persistence than + # /tmp, so we re-use it here. + environment."TMPDIR" = "/tmp"; + serviceConfig = { + ExecStart = [ + " " # required to unset the previous value. + # When running in the initrd, systemd-repart by default searches + # for definition files in /sysroot or /sysusr. We tell it to look + # in the initrd itself. + ''${config.boot.initrd.systemd.package}/bin/systemd-repart \ --definitions=/etc/repart.d \ - --dry-run=no - '' - ]; + --dry-run=no ${lib.optionalString (initrdCfg.device != null) initrdCfg.device} + '' + ]; + }; + # systemd-repart needs to run after /sysroot (or /sysuser, but we + # don't have it) has been mounted because otherwise it cannot + # determine the device (i.e disk) to operate on. If you want to run + # systemd-repart without /sysroot (i.e. to create the root + # partition), you have to explicitly tell it which device to operate + # on. The service then needs to be ordered to run after this device + # is available. + requires = lib.mkIf (initrdCfg.device != null) [ deviceUnit ]; + after = + if initrdCfg.device == null then + [ "sysroot.mount" ] + else + [ deviceUnit ]; }; - # systemd-repart needs to run after /sysroot (or /sysuser, but we don't - # have it) has been mounted because otherwise it cannot determine the - # device (i.e disk) to operate on. If you want to run systemd-repart - # without /sysroot, you have to explicitly tell it which device to - # operate on. - after = [ "sysroot.mount" ]; - }; }; environment.etc = lib.mkIf cfg.enable { diff --git a/nixos/tests/systemd-repart.nix b/nixos/tests/systemd-repart.nix index 36de5d988fdb..9f5867c0f7dd 100644 --- a/nixos/tests/systemd-repart.nix +++ b/nixos/tests/systemd-repart.nix @@ -56,8 +56,8 @@ let # 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 + # This creates two partitions, an ESP available as /dev/vda1 and the root + # partition available as /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 @@ -131,4 +131,62 @@ in assert "Growing existing partition 1." in systemd_repart_logs ''; }; + + create-root = makeTest { + name = "systemd-repart-create-root"; + meta.maintainers = with maintainers; [ nikstur ]; + + nodes.machine = { config, lib, pkgs, ... }: { + virtualisation.useDefaultFilesystems = false; + virtualisation.fileSystems = { + "/" = { + device = "/dev/disk/by-partlabel/created-root"; + fsType = "ext4"; + }; + "/nix/store" = { + device = "/dev/vda2"; + fsType = "ext4"; + }; + }; + + # Create an image containing only the Nix store. This enables creating + # the root partition with systemd-repart and then successfully booting + # into a working system. + # + # This creates two partitions, an ESP available as /dev/vda1 and the Nix + # store available as /dev/vda2. + system.build.diskImage = import ../lib/make-disk-image.nix { + inherit config pkgs lib; + onlyNixStore = true; + format = "raw"; + bootSize = "32M"; + additionalSpace = "0M"; + partitionTableType = "efi"; + installBootLoader = false; + copyChannel = false; + }; + + boot.initrd.systemd.enable = true; + + boot.initrd.systemd.repart.enable = true; + boot.initrd.systemd.repart.device = "/dev/vda"; + systemd.repart.partitions = { + "10-root" = { + Type = "root"; + Label = "created-root"; + Format = "ext4"; + }; + }; + }; + + 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 "Adding new partition 2 to partition table." in systemd_repart_logs + ''; + }; }