nixpkgs/nixos/modules/image/repart.nix
Vincent Haupert 7f70abf80c nixos/image: write systemd-repart output to $out/repart-output.json
Write the output of `systemd-repart` as a JSON file to
`$out/repart-output.json`.

Depending on the repart configuration, the output of `systemd-repart`
contains important information, for example, when creating verity
partitions:

> The verity root hash itself will be included in the output of
> systemd-repart.

See `Verity=` in repart.d(5).
2023-08-02 00:10:14 +02:00

209 lines
6.1 KiB
Nix

# This module exposes options to build a disk image with a GUID Partition Table
# (GPT). It uses systemd-repart to build the image.
{ config, pkgs, lib, utils, ... }:
let
cfg = config.image.repart;
partitionOptions = {
options = {
storePaths = lib.mkOption {
type = with lib.types; listOf path;
default = [ ];
description = lib.mdDoc "The store paths to include in the partition.";
};
stripNixStorePrefix = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Whether to strip `/nix/store/` from the store paths. This is useful
when you want to build a partition that only contains store paths and
is mounted under `/nix/store`.
'';
};
contents = lib.mkOption {
type = with lib.types; attrsOf (submodule {
options = {
source = lib.mkOption {
type = types.path;
description = lib.mdDoc "Path of the source file.";
};
};
});
default = { };
example = lib.literalExpression '' {
"/EFI/BOOT/BOOTX64.EFI".source =
"''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
"/loader/entries/nixos.conf".source = systemdBootEntry;
}
'';
description = lib.mdDoc "The contents to end up in the filesystem image.";
};
repartConfig = lib.mkOption {
type = with lib.types; attrsOf (oneOf [ str int bool ]);
example = {
Type = "home";
SizeMinBytes = "512M";
SizeMaxBytes = "2G";
};
description = lib.mdDoc ''
Specify the repart options for a partiton as a structural setting.
See <https://www.freedesktop.org/software/systemd/man/repart.d.html>
for all available options.
'';
};
};
};
in
{
options.image.repart = {
name = lib.mkOption {
type = lib.types.str;
description = lib.mdDoc "The name of the image.";
};
seed = lib.mkOption {
type = with lib.types; nullOr str;
# Generated with `uuidgen`. Random but fixed to improve reproducibility.
default = "0867da16-f251-457d-a9e8-c31f9a3c220b";
description = lib.mdDoc ''
A UUID to use as a seed. You can set this to `null` to explicitly
randomize the partition UUIDs.
'';
};
split = lib.mkOption {
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Enables generation of split artifacts from partitions. If enabled, for
each partition with SplitName= set, a separate output file containing
just the contents of that partition is generated.
'';
};
partitions = lib.mkOption {
type = with lib.types; attrsOf (submodule partitionOptions);
default = { };
example = lib.literalExpression '' {
"10-esp" = {
contents = {
"/EFI/BOOT/BOOTX64.EFI".source =
"''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
}
repartConfig = {
Type = "esp";
Format = "fat";
};
};
"20-root" = {
storePaths = [ config.system.build.toplevel ];
repartConfig = {
Type = "root";
Format = "ext4";
Minimize = "guess";
};
};
};
'';
description = lib.mdDoc ''
Specify partitions as a set of the names of the partitions with their
configuration as the key.
'';
};
};
config = {
system.build.image =
let
fileSystemToolMapping = with pkgs; {
"vfat" = [ dosfstools mtools ];
"ext4" = [ e2fsprogs.bin ];
"squashfs" = [ squashfsTools ];
"erofs" = [ erofs-utils ];
"btrfs" = [ btrfs-progs ];
"xfs" = [ xfsprogs ];
};
fileSystems = lib.filter
(f: f != null)
(lib.mapAttrsToList (_n: v: v.repartConfig.Format or null) cfg.partitions);
fileSystemTools = builtins.concatMap (f: fileSystemToolMapping."${f}") fileSystems;
makeClosure = paths: pkgs.closureInfo { rootPaths = paths; };
# Add the closure of the provided Nix store paths to cfg.partitions so
# that amend-repart-definitions.py can read it.
addClosure = _name: partitionConfig: partitionConfig // (
lib.optionalAttrs
(partitionConfig.storePaths or [ ] != [ ])
{ closure = "${makeClosure partitionConfig.storePaths}/store-paths"; }
);
finalPartitions = lib.mapAttrs addClosure cfg.partitions;
amendRepartDefinitions = pkgs.runCommand "amend-repart-definitions.py"
{
nativeBuildInputs = with pkgs; [ black ruff mypy ];
buildInputs = [ pkgs.python3 ];
} ''
install ${./amend-repart-definitions.py} $out
patchShebangs --host $out
black --check --diff $out
ruff --line-length 88 $out
mypy --strict $out
'';
format = pkgs.formats.ini { };
definitionsDirectory = utils.systemdUtils.lib.definitions
"repart.d"
format
(lib.mapAttrs (_n: v: { Partition = v.repartConfig; }) finalPartitions);
partitions = pkgs.writeText "partitions.json" (builtins.toJSON finalPartitions);
in
pkgs.runCommand cfg.name
{
nativeBuildInputs = with pkgs; [
fakeroot
systemd
] ++ fileSystemTools;
} ''
amendedRepartDefinitions=$(${amendRepartDefinitions} ${partitions} ${definitionsDirectory})
mkdir -p $out
cd $out
fakeroot systemd-repart \
--dry-run=no \
--empty=create \
--size=auto \
--seed="${cfg.seed}" \
--definitions="$amendedRepartDefinitions" \
--split="${lib.boolToString cfg.split}" \
--json=pretty \
image.raw \
| tee repart-output.json
'';
meta = {
maintainers = with lib.maintainers; [ nikstur ];
doc = ./repart.md;
};
};
}