1# This is an expression meant to be called from `./repart.nix`, it is NOT a
2# NixOS module that can be imported.
3
4{ lib
5, stdenvNoCC
6, runCommand
7, python3
8, black
9, ruff
10, mypy
11, systemd
12, fakeroot
13, util-linux
14
15 # filesystem tools
16, dosfstools
17, mtools
18, e2fsprogs
19, squashfsTools
20, erofs-utils
21, btrfs-progs
22, xfsprogs
23
24 # compression tools
25, zstd
26, xz
27
28 # arguments
29, name
30, version
31, imageFileBasename
32, compression
33, fileSystems
34, partitionsJSON
35, split
36, seed
37, definitionsDirectory
38, sectorSize
39, mkfsEnv ? {}
40, createEmpty ? true
41}:
42
43let
44 systemdArch = let
45 inherit (stdenvNoCC) hostPlatform;
46 in
47 if hostPlatform.isAarch32 then "arm"
48 else if hostPlatform.isAarch64 then "arm64"
49 else if hostPlatform.isx86_32 then "x86"
50 else if hostPlatform.isx86_64 then "x86-64"
51 else if hostPlatform.isMips32 then "mips-le"
52 else if hostPlatform.isMips64 then "mips64-le"
53 else if hostPlatform.isPower then "ppc"
54 else if hostPlatform.isPower64 then "ppc64"
55 else if hostPlatform.isRiscV32 then "riscv32"
56 else if hostPlatform.isRiscV64 then "riscv64"
57 else if hostPlatform.isS390 then "s390"
58 else if hostPlatform.isS390x then "s390x"
59 else if hostPlatform.isLoongArch64 then "loongarch64"
60 else if hostPlatform.isAlpha then "alpha"
61 else hostPlatform.parsed.cpu.name;
62
63 amendRepartDefinitions = runCommand "amend-repart-definitions.py"
64 {
65 # TODO: ruff does not splice properly in nativeBuildInputs
66 depsBuildBuild = [ ruff ];
67 nativeBuildInputs = [ python3 black mypy ];
68 } ''
69 install ${./amend-repart-definitions.py} $out
70 patchShebangs --build $out
71
72 black --check --diff $out
73 ruff --line-length 88 $out
74 mypy --strict $out
75 '';
76
77 fileSystemToolMapping = {
78 "vfat" = [ dosfstools mtools ];
79 "ext4" = [ e2fsprogs.bin ];
80 "squashfs" = [ squashfsTools ];
81 "erofs" = [ erofs-utils ];
82 "btrfs" = [ btrfs-progs ];
83 "xfs" = [ xfsprogs ];
84 };
85
86 fileSystemTools = builtins.concatMap (f: fileSystemToolMapping."${f}") fileSystems;
87
88 compressionPkg = {
89 "zstd" = zstd;
90 "xz" = xz;
91 }."${compression.algorithm}";
92
93 compressionCommand = {
94 "zstd" = "zstd --no-progress --threads=0 -${toString compression.level}";
95 "xz" = "xz --keep --verbose --threads=0 -${toString compression.level}";
96 }."${compression.algorithm}";
97in
98 stdenvNoCC.mkDerivation (finalAttrs:
99 (if (version != null)
100 then { pname = name; inherit version; }
101 else { inherit name; }
102 ) // {
103 __structuredAttrs = true;
104
105 nativeBuildInputs = [
106 systemd
107 fakeroot
108 util-linux
109 ] ++ lib.optionals (compression.enable) [
110 compressionPkg
111 ] ++ fileSystemTools;
112
113 env = mkfsEnv;
114
115 inherit partitionsJSON definitionsDirectory;
116
117 # relative path to the repart definitions that are read by systemd-repart
118 finalRepartDefinitions = "repart.d";
119
120 systemdRepartFlags = [
121 "--architecture=${systemdArch}"
122 "--dry-run=no"
123 "--size=auto"
124 "--seed=${seed}"
125 "--definitions=${finalAttrs.finalRepartDefinitions}"
126 "--split=${lib.boolToString split}"
127 "--json=pretty"
128 ] ++ lib.optionals createEmpty [
129 "--empty=create"
130 ] ++ lib.optionals (sectorSize != null) [
131 "--sector-size=${toString sectorSize}"
132 ];
133
134 dontUnpack = true;
135 dontConfigure = true;
136 doCheck = false;
137
138 patchPhase = ''
139 runHook prePatch
140
141 amendedRepartDefinitionsDir=$(${amendRepartDefinitions} $partitionsJSON $definitionsDirectory)
142 ln -vs $amendedRepartDefinitionsDir $finalRepartDefinitions
143
144 runHook postPatch
145 '';
146
147 buildPhase = ''
148 runHook preBuild
149
150 echo "Building image with systemd-repart..."
151 unshare --map-root-user fakeroot systemd-repart \
152 ''${systemdRepartFlags[@]} \
153 ${imageFileBasename}.raw \
154 | tee repart-output.json
155
156 runHook postBuild
157 '';
158
159 installPhase = ''
160 runHook preInstall
161
162 mkdir -p $out
163 ''
164 # Compression is implemented in the same derivation as opposed to in a
165 # separate derivation to allow users to save disk space. Disk images are
166 # already very space intensive so we want to allow users to mitigate this.
167 + lib.optionalString compression.enable
168 ''
169 for f in ${imageFileBasename}*; do
170 echo "Compressing $f with ${compression.algorithm}..."
171 # Keep the original file when compressing and only delete it afterwards
172 ${compressionCommand} $f && rm $f
173 done
174 '' + ''
175 mv -v repart-output.json ${imageFileBasename}* $out
176
177 runHook postInstall
178 '';
179
180 passthru = {
181 inherit amendRepartDefinitions;
182 };
183})