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