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 imageFileBasename,
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 --quiet --max-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 [
153 systemd
154 util-linux
155 fakeroot
156 ]
157 ++ lib.optionals (compression.enable) [
158 compressionPkg
159 ]
160 ++ fileSystemTools;
161
162 env = mkfsEnv;
163
164 inherit finalPartitions definitionsDirectory;
165
166 partitionsJSON = builtins.toJSON finalAttrs.finalPartitions;
167
168 # relative path to the repart definitions that are read by systemd-repart
169 finalRepartDefinitions = "repart.d";
170
171 systemdRepartFlags =
172 [
173 "--architecture=${systemdArch}"
174 "--dry-run=no"
175 "--size=auto"
176 "--seed=${seed}"
177 "--definitions=${finalAttrs.finalRepartDefinitions}"
178 "--split=${lib.boolToString split}"
179 "--json=pretty"
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 doCheck = false;
191
192 patchPhase = ''
193 runHook prePatch
194
195 amendedRepartDefinitionsDir=$(${amendRepartDefinitions} <(echo "$partitionsJSON") $definitionsDirectory)
196 ln -vs $amendedRepartDefinitionsDir $finalRepartDefinitions
197
198 runHook postPatch
199 '';
200
201 buildPhase = ''
202 runHook preBuild
203
204 echo "Building image with systemd-repart..."
205 unshare --map-root-user fakeroot systemd-repart \
206 ''${systemdRepartFlags[@]} \
207 ${imageFileBasename}.raw \
208 | tee repart-output.json
209
210 runHook postBuild
211 '';
212
213 installPhase =
214 ''
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 ${imageFileBasename}*; 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 ${imageFileBasename}* $out
231
232 runHook postInstall
233 '';
234
235 passthru = {
236 inherit amendRepartDefinitions;
237 };
238 }
239)