nixos/repart-image: refactor to use mkDerivation

As a follow-up to https://github.com/NixOS/nixpkgs/pull/294096 this
should further improve the flexibility around building OS images with
systemd-repart:

* Previously the attribute set `compression` needed to be fully
  populated, including `algorithm` and `level` because
  `compression.enable` was evaluated by bash, after being interpolated
  as strings into the `buildCommand`. Now it's sufficient to pass
  `compression.enable = false` to the builder, e.g. in `overrideAttrs`,
  to disable the compression.
* Using mkDerivation allows for much more customization than the
  previously used `runCommand`, making use of phases and pre/post hooks.
  This is especially helpful for building multiple images from the same
  system configuration, e.g. to build an image `Y` based on a partially
  built raw image `X`,  by injecting a UKI that depends on `X` into a
  defered ESP.
* Before this change it was non-trivial to conduct further manipulations
  on the amended repart definitions. Now, the definitions that
  systemd-repart uses to build the image can be easily manipulated in
  `postPatch` or `preBuild`.

Aside from this, the build is now executed in the build directory, rather
than `$out`. This allows references to relative paths in the build
environment to be used, especially for `--definitions`, which previously
required an absolute path.
This commit is contained in:
WilliButz 2024-03-19 17:04:47 +01:00
parent 1357b820aa
commit d7ef2defda
No known key found for this signature in database
GPG Key ID: AB05DF703EB9DC70
2 changed files with 62 additions and 29 deletions

View File

@ -2,8 +2,8 @@
# NixOS module that can be imported.
{ lib
, stdenvNoCC
, runCommand
, runCommandLocal
, python3
, black
, ruff
@ -26,15 +26,18 @@
, xz
# arguments
, name
, version
, imageFileBasename
, compression
, fileSystems
, partitions
, partitionsJSON
, split
, seed
, definitionsDirectory
, sectorSize
, mkfsEnv ? {}
, createEmpty ? true
}:
let
@ -52,11 +55,6 @@ let
mypy --strict $out
'';
amendedRepartDefinitions = runCommandLocal "amended-repart.d" {} ''
definitions=$(${amendRepartDefinitions} ${partitions} ${definitionsDirectory})
cp -r $definitions $out
'';
fileSystemToolMapping = {
"vfat" = [ dosfstools mtools ];
"ext4" = [ e2fsprogs.bin ];
@ -78,53 +76,88 @@ let
"xz" = "xz --keep --verbose --threads=0 -${toString compression.level}";
}."${compression.algorithm}";
in
runCommand imageFileBasename
{
stdenvNoCC.mkDerivation (finalAttrs:
(if (version != null)
then { pname = name; inherit version; }
else { inherit name; }
) // {
__structuredAttrs = true;
nativeBuildInputs = [
systemd
fakeroot
util-linux
] ++ lib.optionals (compression.enable) [
compressionPkg
] ++ fileSystemTools;
env = mkfsEnv;
inherit partitionsJSON definitionsDirectory;
# relative path to the repart definitions that are read by systemd-repart
finalRepartDefinitions = "repart.d";
systemdRepartFlags = [
"--dry-run=no"
"--empty=create"
"--size=auto"
"--seed=${seed}"
"--definitions=${amendedRepartDefinitions}"
"--definitions=${finalAttrs.finalRepartDefinitions}"
"--split=${lib.boolToString split}"
"--json=pretty"
] ++ lib.optionals createEmpty [
"--empty=create"
] ++ lib.optionals (sectorSize != null) [
"--sector-size=${toString sectorSize}"
];
passthru = {
inherit amendRepartDefinitions amendedRepartDefinitions;
};
} ''
mkdir -p $out
cd $out
dontUnpack = true;
dontConfigure = true;
doCheck = false;
echo "Building image with systemd-repart..."
unshare --map-root-user fakeroot systemd-repart \
''${systemdRepartFlags[@]} \
${imageFileBasename}.raw \
| tee repart-output.json
patchPhase = ''
runHook prePatch
amendedRepartDefinitionsDir=$(${amendRepartDefinitions} $partitionsJSON $definitionsDirectory)
ln -vs $amendedRepartDefinitionsDir $finalRepartDefinitions
runHook postPatch
'';
buildPhase = ''
runHook preBuild
echo "Building image with systemd-repart..."
unshare --map-root-user fakeroot systemd-repart \
''${systemdRepartFlags[@]} \
${imageFileBasename}.raw \
| tee repart-output.json
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out
''
# Compression is implemented in the same derivation as opposed to in a
# separate derivation to allow users to save disk space. Disk images are
# already very space intensive so we want to allow users to mitigate this.
if ${lib.boolToString compression.enable}; then
+ lib.optionalString compression.enable
''
for f in ${imageFileBasename}*; do
echo "Compressing $f with ${compression.algorithm}..."
# Keep the original file when compressing and only delete it afterwards
${compressionCommand} $f && rm $f
done
fi
''
'' + ''
mv -v repart-output.json ${imageFileBasename}* $out
runHook postInstall
'';
passthru = {
inherit amendRepartDefinitions;
};
})

View File

@ -266,14 +266,14 @@ in
format
(lib.mapAttrs (_n: v: { Partition = v.repartConfig; }) finalPartitions);
partitions = pkgs.writeText "partitions.json" (builtins.toJSON finalPartitions);
partitionsJSON = pkgs.writeText "partitions.json" (builtins.toJSON finalPartitions);
mkfsEnv = mkfsOptionsToEnv cfg.mkfsOptions;
in
pkgs.callPackage ./repart-image.nix {
systemd = cfg.package;
inherit (cfg) imageFileBasename compression split seed sectorSize;
inherit fileSystems definitionsDirectory partitions mkfsEnv;
inherit (cfg) name version imageFileBasename compression split seed sectorSize;
inherit fileSystems definitionsDirectory partitionsJSON mkfsEnv;
};
meta.maintainers = with lib.maintainers; [ nikstur ];