1{
2 lib,
3 pkgs,
4 customQemu ? null,
5 kernel ? pkgs.linux,
6 img ? pkgs.stdenv.hostPlatform.linux-kernel.target,
7 storeDir ? builtins.storeDir,
8 rootModules ? [
9 "virtio_pci"
10 "virtio_mmio"
11 "virtio_blk"
12 "virtio_balloon"
13 "virtio_rng"
14 "ext4"
15 "virtiofs"
16 "crc32c_generic"
17 ],
18}:
19
20let
21 inherit (pkgs)
22 bash
23 bashInteractive
24 busybox
25 cpio
26 coreutils
27 e2fsprogs
28 fetchurl
29 kmod
30 rpm
31 stdenv
32 util-linux
33 buildPackages
34 writeScript
35 writeText
36 runCommand
37 ;
38in
39rec {
40 qemu-common = import ../../../nixos/lib/qemu-common.nix { inherit lib pkgs; };
41
42 qemu = buildPackages.qemu_kvm;
43
44 modulesClosure = pkgs.makeModulesClosure {
45 kernel = lib.getOutput "modules" kernel;
46 inherit rootModules;
47 firmware = kernel;
48 };
49
50 hd = "vda"; # either "sda" or "vda"
51
52 initrdUtils =
53 runCommand "initrd-utils"
54 {
55 nativeBuildInputs = [ buildPackages.nukeReferences ];
56 allowedReferences = [
57 "out"
58 modulesClosure
59 ]; # prevent accidents like glibc being included in the initrd
60 }
61 ''
62 mkdir -p $out/bin
63 mkdir -p $out/lib
64
65 # Copy what we need from Glibc.
66 cp -p \
67 ${pkgs.stdenv.cc.libc}/lib/ld-*.so.? \
68 ${pkgs.stdenv.cc.libc}/lib/libc.so.* \
69 ${pkgs.stdenv.cc.libc}/lib/libm.so.* \
70 ${pkgs.stdenv.cc.libc}/lib/libresolv.so.* \
71 ${pkgs.stdenv.cc.libc}/lib/libpthread.so.* \
72 ${pkgs.zstd.out}/lib/libzstd.so.* \
73 ${pkgs.xz.out}/lib/liblzma.so.* \
74 $out/lib
75
76 # Copy BusyBox.
77 cp -pd ${pkgs.busybox}/bin/* $out/bin
78 cp -pd ${pkgs.kmod}/bin/* $out/bin
79
80 # Run patchelf to make the programs refer to the copied libraries.
81 for i in $out/bin/* $out/lib/*; do if ! test -L $i; then nuke-refs $i; fi; done
82
83 for i in $out/bin/*; do
84 if [ -f "$i" -a ! -L "$i" ]; then
85 echo "patching $i..."
86 patchelf --set-interpreter $out/lib/ld-*.so.? --set-rpath $out/lib $i || true
87 fi
88 done
89
90 find $out/lib -type f \! -name 'ld*.so.?' | while read i; do
91 echo "patching $i..."
92 patchelf --set-rpath $out/lib $i
93 done
94 ''; # */
95
96 stage1Init = writeScript "vm-run-stage1" ''
97 #! ${initrdUtils}/bin/ash -e
98
99 export PATH=${initrdUtils}/bin
100
101 mkdir /etc
102 echo -n > /etc/fstab
103
104 mount -t proc none /proc
105 mount -t sysfs none /sys
106
107 echo 2 > /proc/sys/vm/panic_on_oom
108
109 for o in $(cat /proc/cmdline); do
110 case $o in
111 mountDisk=*)
112 mountDisk=''${mountDisk#mountDisk=}
113 ;;
114 command=*)
115 set -- $(IFS==; echo $o)
116 command=$2
117 ;;
118 esac
119 done
120
121 echo "loading kernel modules..."
122 for i in $(cat ${modulesClosure}/insmod-list); do
123 insmod $i || echo "warning: unable to load $i"
124 done
125
126 mount -t devtmpfs devtmpfs /dev
127 ln -s /proc/self/fd /dev/fd
128 ln -s /proc/self/fd/0 /dev/stdin
129 ln -s /proc/self/fd/1 /dev/stdout
130 ln -s /proc/self/fd/2 /dev/stderr
131
132 ifconfig lo up
133
134 mkdir /fs
135
136 if test -z "$mountDisk"; then
137 mount -t tmpfs none /fs
138 elif [[ -e "$mountDisk" ]]; then
139 mount "$mountDisk" /fs
140 else
141 mount /dev/${hd} /fs
142 fi
143
144 mkdir -p /fs/dev
145 mount -o bind /dev /fs/dev
146
147 mkdir -p /fs/dev/shm /fs/dev/pts
148 mount -t tmpfs -o "mode=1777" none /fs/dev/shm
149 mount -t devpts none /fs/dev/pts
150
151 echo "mounting Nix store..."
152 mkdir -p /fs${storeDir}
153 mount -t virtiofs store /fs${storeDir}
154
155 mkdir -p /fs/tmp /fs/run /fs/var
156 mount -t tmpfs -o "mode=1777" none /fs/tmp
157 mount -t tmpfs -o "mode=755" none /fs/run
158 ln -sfn /run /fs/var/run
159
160 echo "mounting host's temporary directory..."
161 mkdir -p /fs/tmp/xchg
162 mount -t virtiofs xchg /fs/tmp/xchg
163
164 mkdir -p /fs/proc
165 mount -t proc none /fs/proc
166
167 mkdir -p /fs/sys
168 mount -t sysfs none /fs/sys
169
170 mkdir -p /fs/etc
171 ln -sf /proc/mounts /fs/etc/mtab
172 echo "127.0.0.1 localhost" > /fs/etc/hosts
173 # Ensures tools requiring /etc/passwd will work (e.g. nix)
174 if [ ! -e /fs/etc/passwd ]; then
175 echo "root:x:0:0:System administrator:/root:/bin/sh" > /fs/etc/passwd
176 fi
177
178 echo "starting stage 2 ($command)"
179 exec switch_root /fs $command
180 '';
181
182 initrd = pkgs.makeInitrd {
183 contents = [
184 {
185 object = stage1Init;
186 symlink = "/init";
187 }
188 ];
189 };
190
191 stage2Init = writeScript "vm-run-stage2" ''
192 #! ${bash}/bin/sh
193 set -euo pipefail
194 source /tmp/xchg/saved-env
195 if [ -f /tmp/xchg/.attrs.sh ]; then
196 source /tmp/xchg/.attrs.sh
197 export NIX_ATTRS_JSON_FILE=/tmp/xchg/.attrs.json
198 export NIX_ATTRS_SH_FILE=/tmp/xchg/.attrs.sh
199 fi
200
201 export NIX_STORE=${storeDir}
202 export NIX_BUILD_TOP=/tmp
203 export TMPDIR=/tmp
204 export PATH=/empty
205 cd "$NIX_BUILD_TOP"
206
207 source $stdenv/setup
208
209 if ! test -e /bin/sh; then
210 ${coreutils}/bin/mkdir -p /bin
211 ${coreutils}/bin/ln -s ${bash}/bin/sh /bin/sh
212 fi
213
214 # Set up automatic kernel module loading.
215 export MODULE_DIR=${lib.getOutput "modules" kernel}/lib/modules/
216 ${coreutils}/bin/cat <<EOF > /run/modprobe
217 #! ${bash}/bin/sh
218 export MODULE_DIR=$MODULE_DIR
219 exec ${kmod}/bin/modprobe "\$@"
220 EOF
221 ${coreutils}/bin/chmod 755 /run/modprobe
222 echo /run/modprobe > /proc/sys/kernel/modprobe
223
224 # For debugging: if this is the second time this image is run,
225 # then don't start the build again, but instead drop the user into
226 # an interactive shell.
227 if test -n "$origBuilder" -a ! -e /.debug; then
228 exec < /dev/null
229 ${coreutils}/bin/touch /.debug
230 declare -a argsArray=()
231 concatTo argsArray origArgs
232 "$origBuilder" "''${argsArray[@]}"
233 echo $? > /tmp/xchg/in-vm-exit
234
235 ${busybox}/bin/mount -o remount,ro dummy /
236
237 ${busybox}/bin/poweroff -f
238 else
239 export PATH=/bin:/usr/bin:${coreutils}/bin
240 echo "Starting interactive shell..."
241 echo "(To run the original builder: \$origBuilder \$origArgs)"
242 exec ${busybox}/bin/setsid ${bashInteractive}/bin/bash < /dev/${qemu-common.qemuSerialDevice} &> /dev/${qemu-common.qemuSerialDevice}
243 fi
244 '';
245
246 qemuCommandLinux = ''
247 ${if (customQemu != null) then customQemu else (qemu-common.qemuBinary qemu)} \
248 -nographic -no-reboot \
249 -device virtio-rng-pci \
250 -chardev socket,id=store,path=virtio-store.sock \
251 -device vhost-user-fs-pci,chardev=store,tag=store \
252 -chardev socket,id=xchg,path=virtio-xchg.sock \
253 -device vhost-user-fs-pci,chardev=xchg,tag=xchg \
254 ''${diskImage:+-drive file=$diskImage,if=virtio,cache=unsafe,werror=report} \
255 -kernel ${kernel}/${img} \
256 -initrd ${initrd}/initrd \
257 -append "console=${qemu-common.qemuSerialDevice} panic=1 command=${stage2Init} mountDisk=$mountDisk loglevel=4" \
258 $QEMU_OPTS
259 '';
260
261 vmRunCommand =
262 qemuCommand:
263 writeText "vm-run" ''
264 ${coreutils}/bin/mkdir xchg
265 export > xchg/saved-env
266
267 if [ -f "''${NIX_ATTRS_SH_FILE-}" ]; then
268 ${coreutils}/bin/cp $NIX_ATTRS_JSON_FILE $NIX_ATTRS_SH_FILE xchg
269 source "$NIX_ATTRS_SH_FILE"
270 fi
271 source $stdenv/setup
272
273 eval "$preVM"
274
275 if [ "$enableParallelBuilding" = 1 ]; then
276 QEMU_NR_VCPUS=0
277 if [ ''${NIX_BUILD_CORES:-0} = 0 ]; then
278 QEMU_NR_VCPUS="$(nproc)"
279 else
280 QEMU_NR_VCPUS="$NIX_BUILD_CORES"
281 fi
282 # qemu only supports 255 vCPUs (see error from `qemu-system-x86_64 -smp 256`)
283 if [ "$QEMU_NR_VCPUS" -gt 255 ]; then
284 QEMU_NR_VCPUS=255
285 fi
286 QEMU_OPTS+=" -smp cpus=$QEMU_NR_VCPUS"
287 fi
288
289 # Write the command to start the VM to a file so that the user can
290 # debug inside the VM if the build fails (when Nix is called with
291 # the -K option to preserve the temporary build directory).
292 ${coreutils}/bin/cat > ./run-vm <<EOF
293 #! ${bash}/bin/sh
294 ''${diskImage:+diskImage=$diskImage}
295 # GitHub Actions runners seems to not allow installing seccomp filter: https://github.com/rcambrj/nix-pi-loader/issues/1#issuecomment-2605497516
296 # Since we are running in a sandbox already, the difference between seccomp and none is minimal
297 ${pkgs.virtiofsd}/bin/virtiofsd --xattr --socket-path virtio-store.sock --sandbox none --seccomp none --shared-dir "${storeDir}" &
298 ${pkgs.virtiofsd}/bin/virtiofsd --xattr --socket-path virtio-xchg.sock --sandbox none --seccomp none --shared-dir xchg &
299 ${qemuCommand}
300 EOF
301
302 ${coreutils}/bin/chmod +x ./run-vm
303 source ./run-vm
304
305 if ! test -e xchg/in-vm-exit; then
306 echo "Virtual machine didn't produce an exit code."
307 exit 1
308 fi
309
310 exitCode="$(${coreutils}/bin/cat xchg/in-vm-exit)"
311 if [ "$exitCode" != "0" ]; then
312 exit "$exitCode"
313 fi
314
315 eval "$postVM"
316 '';
317
318 # A bash script fragment that produces a disk image at `destination`.
319 createEmptyImage =
320 {
321 # Disk image size in MiB (1024*1024 bytes)
322 size,
323 # Name that will be written to ${destination}/nix-support/full-name
324 fullName,
325 # Where to write the image files, defaulting to $out
326 destination ? "$out",
327 }:
328 ''
329 mkdir -p ${destination}
330 diskImage=${destination}/disk-image.qcow2
331 ${qemu}/bin/qemu-img create -f qcow2 $diskImage "${toString size}M"
332
333 mkdir ${destination}/nix-support
334 echo "${fullName}" > ${destination}/nix-support/full-name
335 '';
336
337 defaultCreateRootFS = ''
338 mkdir /mnt
339 ${e2fsprogs}/bin/mkfs.ext4 /dev/${hd}
340 ${util-linux}/bin/mount -t ext4 /dev/${hd} /mnt
341
342 if test -e /mnt/.debug; then
343 exec ${bash}/bin/sh
344 fi
345 touch /mnt/.debug
346
347 mkdir /mnt/proc /mnt/dev /mnt/sys
348 '';
349
350 /*
351 Run a derivation in a Linux virtual machine (using Qemu/KVM). By
352 default, there is no disk image; the root filesystem is a tmpfs,
353 and the nix store is shared with the host (via the 9P protocol).
354 Thus, any pure Nix derivation should run unmodified, e.g. the
355 call
356
357 runInLinuxVM patchelf
358
359 will build the derivation `patchelf' inside a VM. The attribute
360 `preVM' can optionally contain a shell command to be evaluated
361 *before* the VM is started (i.e., on the host). The attribute
362 `memSize' specifies the memory size of the VM in MiB (1024*1024
363 bytes), defaulting to 512. The attribute `diskImage' can
364 optionally specify a file system image to be attached to /dev/sda.
365 (Note that currently we expect the image to contain a filesystem,
366 not a full disk image with a partition table etc.)
367
368 If the build fails and Nix is run with the `-K' option, a script
369 `run-vm' will be left behind in the temporary build directory
370 that allows you to boot into the VM and debug it interactively.
371 */
372
373 runInLinuxVM =
374 drv:
375 lib.overrideDerivation drv (
376 {
377 memSize ? 512,
378 QEMU_OPTS ? "",
379 args,
380 builder,
381 ...
382 }:
383 {
384 requiredSystemFeatures = [ "kvm" ];
385 builder = "${bash}/bin/sh";
386 args = [
387 "-e"
388 (vmRunCommand qemuCommandLinux)
389 ];
390 origArgs = args;
391 origBuilder = builder;
392 QEMU_OPTS = "${QEMU_OPTS} -m ${toString memSize} -object memory-backend-memfd,id=mem,size=${toString memSize}M,share=on -machine memory-backend=mem";
393 passAsFile = [ ]; # HACK fix - see https://github.com/NixOS/nixpkgs/issues/16742
394 }
395 );
396
397 extractFs =
398 {
399 file,
400 fs ? null,
401 }:
402 runInLinuxVM (
403 stdenv.mkDerivation {
404 name = "extract-file";
405 buildInputs = [ util-linux ];
406 buildCommand = ''
407 ln -s ${kernel}/lib /lib
408 ${kmod}/bin/modprobe loop
409 ${kmod}/bin/modprobe ext4
410 ${kmod}/bin/modprobe hfs
411 ${kmod}/bin/modprobe hfsplus
412 ${kmod}/bin/modprobe squashfs
413 ${kmod}/bin/modprobe iso9660
414 ${kmod}/bin/modprobe ufs
415 ${kmod}/bin/modprobe cramfs
416
417 mkdir -p $out
418 mkdir -p tmp
419 mount -o loop,ro,ufstype=44bsd ${lib.optionalString (fs != null) "-t ${fs} "}${file} tmp ||
420 mount -o loop,ro ${lib.optionalString (fs != null) "-t ${fs} "}${file} tmp
421 cp -Rv tmp/* $out/ || exit 0
422 '';
423 }
424 );
425
426 extractMTDfs =
427 {
428 file,
429 fs ? null,
430 }:
431 runInLinuxVM (
432 stdenv.mkDerivation {
433 name = "extract-file-mtd";
434 buildInputs = [
435 pkgs.util-linux
436 pkgs.mtdutils
437 ];
438 buildCommand = ''
439 ln -s ${kernel}/lib /lib
440 ${kmod}/bin/modprobe mtd
441 ${kmod}/bin/modprobe mtdram total_size=131072
442 ${kmod}/bin/modprobe mtdchar
443 ${kmod}/bin/modprobe mtdblock
444 ${kmod}/bin/modprobe jffs2
445 ${kmod}/bin/modprobe zlib
446
447 mkdir -p $out
448 mkdir -p tmp
449
450 dd if=${file} of=/dev/mtd0
451 mount ${lib.optionalString (fs != null) "-t ${fs} "}/dev/mtdblock0 tmp
452
453 cp -R tmp/* $out/
454 '';
455 }
456 );
457
458 /*
459 Like runInLinuxVM, but run the build not using the stdenv from
460 the Nix store, but using the tools provided by /bin, /usr/bin
461 etc. from the specified filesystem image, which typically is a
462 filesystem containing a non-NixOS Linux distribution.
463 */
464
465 runInLinuxImage =
466 drv:
467 runInLinuxVM (
468 lib.overrideDerivation drv (attrs: {
469 mountDisk = attrs.mountDisk or true;
470
471 /*
472 Mount `image' as the root FS, but use a temporary copy-on-write
473 image since we don't want to (and can't) write to `image'.
474 */
475 preVM = ''
476 diskImage=$(pwd)/disk-image.qcow2
477 origImage=${attrs.diskImage}
478 if test -d "$origImage"; then origImage="$origImage/disk-image.qcow2"; fi
479 ${qemu}/bin/qemu-img create -F ${attrs.diskImageFormat} -b "$origImage" -f qcow2 $diskImage
480 '';
481
482 /*
483 Inside the VM, run the stdenv setup script normally, but at the
484 very end set $PATH and $SHELL to the `native' paths for the
485 distribution inside the VM.
486 */
487 postHook = ''
488 PATH=/usr/bin:/bin:/usr/sbin:/sbin
489 SHELL=/bin/sh
490 eval "$origPostHook"
491 '';
492
493 origPostHook = lib.optionalString (attrs ? postHook) attrs.postHook;
494
495 # Don't run Nix-specific build steps like patchelf.
496 fixupPhase = "true";
497 })
498 );
499
500 /*
501 Create a filesystem image of the specified size and fill it with
502 a set of RPM packages.
503 */
504
505 fillDiskWithRPMs =
506 {
507 size ? 4096,
508 rpms,
509 name,
510 fullName,
511 preInstall ? "",
512 postInstall ? "",
513 runScripts ? true,
514 createRootFS ? defaultCreateRootFS,
515 QEMU_OPTS ? "",
516 memSize ? 512,
517 unifiedSystemDir ? false,
518 }:
519
520 runInLinuxVM (
521 stdenv.mkDerivation {
522 inherit
523 name
524 preInstall
525 postInstall
526 rpms
527 QEMU_OPTS
528 memSize
529 ;
530 preVM = createEmptyImage { inherit size fullName; };
531
532 buildCommand = ''
533 ${createRootFS}
534
535 chroot=$(type -tP chroot)
536
537 # Make the Nix store available in /mnt, because that's where the RPMs live.
538 mkdir -p /mnt${storeDir}
539 ${util-linux}/bin/mount -o bind ${storeDir} /mnt${storeDir}
540 # Some programs may require devices in /dev to be available (e.g. /dev/random)
541 ${util-linux}/bin/mount -o bind /dev /mnt/dev
542
543 # Newer distributions like Fedora 18 require /lib etc. to be
544 # symlinked to /usr.
545 ${lib.optionalString unifiedSystemDir ''
546 mkdir -p /mnt/usr/bin /mnt/usr/sbin /mnt/usr/lib /mnt/usr/lib64
547 ln -s /usr/bin /mnt/bin
548 ln -s /usr/sbin /mnt/sbin
549 ln -s /usr/lib /mnt/lib
550 ln -s /usr/lib64 /mnt/lib64
551 ${util-linux}/bin/mount -t proc none /mnt/proc
552 ''}
553
554 echo "unpacking RPMs..."
555 set +o pipefail
556 for i in $rpms; do
557 echo "$i..."
558 ${rpm}/bin/rpm2cpio "$i" | chroot /mnt ${cpio}/bin/cpio -i --make-directories --unconditional
559 done
560
561 eval "$preInstall"
562
563 echo "initialising RPM DB..."
564 PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \
565 ldconfig -v || true
566 PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \
567 rpm --initdb
568
569 ${util-linux}/bin/mount -o bind /tmp /mnt/tmp
570
571 echo "installing RPMs..."
572 PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \
573 rpm -iv --nosignature ${lib.optionalString (!runScripts) "--noscripts"} $rpms
574
575 echo "running post-install script..."
576 eval "$postInstall"
577
578 rm /mnt/.debug
579
580 ${util-linux}/bin/umount /mnt${storeDir} /mnt/tmp /mnt/dev ${lib.optionalString unifiedSystemDir "/mnt/proc"}
581 ${util-linux}/bin/umount /mnt
582 '';
583
584 passthru = { inherit fullName; };
585 }
586 );
587
588 /*
589 Generate a script that can be used to run an interactive session
590 in the given image.
591 */
592
593 makeImageTestScript =
594 image:
595 writeScript "image-test" ''
596 #! ${bash}/bin/sh
597 if test -z "$1"; then
598 echo "Syntax: $0 <copy-on-write-temp-file>"
599 exit 1
600 fi
601 diskImage="$1"
602 if ! test -e "$diskImage"; then
603 ${qemu}/bin/qemu-img create -b ${image}/disk-image.qcow2 -f qcow2 -F qcow2 "$diskImage"
604 fi
605 export TMPDIR=$(mktemp -d)
606 export out=/dummy
607 export origBuilder=
608 export origArgs=
609 mkdir $TMPDIR/xchg
610 export > $TMPDIR/xchg/saved-env
611 mountDisk=1
612 ${qemuCommandLinux}
613 '';
614
615 /*
616 Build RPM packages from the tarball `src' in the Linux
617 distribution installed in the filesystem `diskImage'. The
618 tarball must contain an RPM specfile.
619 */
620
621 buildRPM =
622 attrs:
623 runInLinuxImage (
624 stdenv.mkDerivation (
625 {
626 prePhases = [
627 "prepareImagePhase"
628 "sysInfoPhase"
629 ];
630 dontConfigure = true;
631
632 outDir = "rpms/${attrs.diskImage.name}";
633
634 prepareImagePhase = ''
635 if test -n "$extraRPMs"; then
636 for rpmdir in $extraRPMs ; do
637 rpm -iv $(ls $rpmdir/rpms/*/*.rpm | grep -v 'src\.rpm' | sort | head -1)
638 done
639 fi
640 '';
641
642 sysInfoPhase = ''
643 echo "System/kernel: $(uname -a)"
644 if test -e /etc/fedora-release; then echo "Fedora release: $(cat /etc/fedora-release)"; fi
645 if test -e /etc/SuSE-release; then echo "SUSE release: $(cat /etc/SuSE-release)"; fi
646 echo "installed RPM packages"
647 rpm -qa --qf "%{Name}-%{Version}-%{Release} (%{Arch}; %{Distribution}; %{Vendor})\n"
648 '';
649
650 buildPhase = ''
651 eval "$preBuild"
652
653 srcName="$(rpmspec --srpm -q --qf '%{source}' *.spec)"
654 cp "$src" "$srcName" # `ln' doesn't work always work: RPM requires that the file is owned by root
655
656 export HOME=/tmp/home
657 mkdir $HOME
658
659 rpmout=/tmp/rpmout
660 mkdir $rpmout $rpmout/SPECS $rpmout/BUILD $rpmout/RPMS $rpmout/SRPMS
661
662 echo "%_topdir $rpmout" >> $HOME/.rpmmacros
663
664 if [ `uname -m` = i686 ]; then extra="--target i686-linux"; fi
665 rpmbuild -vv $extra -ta "$srcName"
666
667 eval "$postBuild"
668 '';
669
670 installPhase = ''
671 eval "$preInstall"
672
673 mkdir -p $out/$outDir
674 find $rpmout -name "*.rpm" -exec cp {} $out/$outDir \;
675
676 for i in $out/$outDir/*.rpm; do
677 echo "Generated RPM/SRPM: $i"
678 rpm -qip $i
679 done
680
681 eval "$postInstall"
682 ''; # */
683 }
684 // attrs
685 )
686 );
687
688 /*
689 Create a filesystem image of the specified size and fill it with
690 a set of Debian packages. `debs' must be a list of list of
691 .deb files, namely, the Debian packages grouped together into
692 strongly connected components. See deb/deb-closure.nix.
693 */
694
695 fillDiskWithDebs =
696 {
697 size ? 4096,
698 debs,
699 name,
700 fullName,
701 postInstall ? null,
702 createRootFS ? defaultCreateRootFS,
703 QEMU_OPTS ? "",
704 memSize ? 512,
705 ...
706 }@args:
707
708 runInLinuxVM (
709 stdenv.mkDerivation (
710 {
711 inherit
712 name
713 postInstall
714 QEMU_OPTS
715 memSize
716 ;
717
718 debs = (lib.intersperse "|" debs);
719
720 preVM = createEmptyImage { inherit size fullName; };
721
722 buildCommand = ''
723 ${createRootFS}
724
725 PATH=$PATH:${
726 lib.makeBinPath [
727 pkgs.dpkg
728 pkgs.glibc
729 pkgs.xz
730 ]
731 }
732
733 # Unpack the .debs. We do this to prevent pre-install scripts
734 # (which have lots of circular dependencies) from barfing.
735 echo "unpacking Debs..."
736
737 for deb in $debs; do
738 if test "$deb" != "|"; then
739 echo "$deb..."
740 dpkg-deb --extract "$deb" /mnt
741 fi
742 done
743
744 # Make the Nix store available in /mnt, because that's where the .debs live.
745 mkdir -p /mnt/inst${storeDir}
746 ${util-linux}/bin/mount -o bind ${storeDir} /mnt/inst${storeDir}
747 ${util-linux}/bin/mount -o bind /proc /mnt/proc
748 ${util-linux}/bin/mount -o bind /dev /mnt/dev
749
750 # Misc. files/directories assumed by various packages.
751 echo "initialising Dpkg DB..."
752 touch /mnt/etc/shells
753 touch /mnt/var/lib/dpkg/status
754 touch /mnt/var/lib/dpkg/available
755 touch /mnt/var/lib/dpkg/diversions
756
757 # Now install the .debs. This is basically just to register
758 # them with dpkg and to make their pre/post-install scripts
759 # run.
760 echo "installing Debs..."
761
762 export DEBIAN_FRONTEND=noninteractive
763
764 oldIFS="$IFS"
765 IFS="|"
766 for component in $debs; do
767 IFS="$oldIFS"
768 echo
769 echo ">>> INSTALLING COMPONENT: $component"
770 debs=
771 for i in $component; do
772 debs="$debs /inst/$i";
773 done
774 chroot=$(type -tP chroot)
775
776 # Create a fake start-stop-daemon script, as done in debootstrap.
777 mv "/mnt/sbin/start-stop-daemon" "/mnt/sbin/start-stop-daemon.REAL"
778 echo "#!/bin/true" > "/mnt/sbin/start-stop-daemon"
779 chmod 755 "/mnt/sbin/start-stop-daemon"
780
781 PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \
782 /usr/bin/dpkg --install --force-all $debs < /dev/null || true
783
784 # Move the real start-stop-daemon back into its place.
785 mv "/mnt/sbin/start-stop-daemon.REAL" "/mnt/sbin/start-stop-daemon"
786 done
787
788 echo "running post-install script..."
789 eval "$postInstall"
790
791 rm /mnt/.debug
792
793 ${util-linux}/bin/umount /mnt/inst${storeDir}
794 ${util-linux}/bin/umount /mnt/proc
795 ${util-linux}/bin/umount /mnt/dev
796 ${util-linux}/bin/umount /mnt
797 '';
798
799 passthru = { inherit fullName; };
800 }
801 // args
802 )
803 );
804
805 /*
806 Generate a Nix expression containing fetchurl calls for the
807 closure of a set of top-level RPM packages from the
808 `primary.xml.gz' file of a Fedora or openSUSE distribution.
809 */
810
811 rpmClosureGenerator =
812 {
813 name,
814 packagesLists,
815 urlPrefixes,
816 packages,
817 archs ? [ ],
818 }:
819 assert (builtins.length packagesLists) == (builtins.length urlPrefixes);
820 runCommand "${name}.nix"
821 {
822 nativeBuildInputs = [
823 buildPackages.perl
824 buildPackages.perlPackages.XMLSimple
825 ];
826 inherit archs;
827 }
828 ''
829 ${lib.concatImapStrings (i: pl: ''
830 gunzip < ${pl} > ./packages_${toString i}.xml
831 '') packagesLists}
832 perl -w ${rpm/rpm-closure.pl} \
833 ${
834 lib.concatImapStrings (i: pl: "./packages_${toString i}.xml ${pl.snd} ") (
835 lib.zipLists packagesLists urlPrefixes
836 )
837 } \
838 ${toString packages} > $out
839 '';
840
841 /*
842 Helper function that combines rpmClosureGenerator and
843 fillDiskWithRPMs to generate a disk image from a set of package
844 names.
845 */
846
847 makeImageFromRPMDist =
848 {
849 name,
850 fullName,
851 size ? 4096,
852 urlPrefix ? "",
853 urlPrefixes ? [ urlPrefix ],
854 packagesList ? "",
855 packagesLists ? [ packagesList ],
856 packages,
857 extraPackages ? [ ],
858 preInstall ? "",
859 postInstall ? "",
860 archs ? [
861 "noarch"
862 "i386"
863 ],
864 runScripts ? true,
865 createRootFS ? defaultCreateRootFS,
866 QEMU_OPTS ? "",
867 memSize ? 512,
868 unifiedSystemDir ? false,
869 }:
870
871 fillDiskWithRPMs {
872 inherit
873 name
874 fullName
875 size
876 preInstall
877 postInstall
878 runScripts
879 createRootFS
880 unifiedSystemDir
881 QEMU_OPTS
882 memSize
883 ;
884 rpms = import (rpmClosureGenerator {
885 inherit
886 name
887 packagesLists
888 urlPrefixes
889 archs
890 ;
891 packages = packages ++ extraPackages;
892 }) { inherit fetchurl; };
893 };
894
895 /*
896 Like `rpmClosureGenerator', but now for Debian/Ubuntu releases
897 (i.e. generate a closure from a Packages.bz2 file).
898 */
899
900 debClosureGenerator =
901 {
902 name,
903 packagesLists,
904 urlPrefix,
905 packages,
906 }:
907
908 runCommand "${name}.nix"
909 {
910 nativeBuildInputs = [
911 buildPackages.perl
912 buildPackages.dpkg
913 buildPackages.nixfmt
914 ];
915 }
916 ''
917 for i in ${toString packagesLists}; do
918 echo "adding $i..."
919 case $i in
920 *.xz | *.lzma)
921 xz -d < $i >> ./Packages
922 ;;
923 *.bz2)
924 bunzip2 < $i >> ./Packages
925 ;;
926 *.gz)
927 gzip -dc < $i >> ./Packages
928 ;;
929 esac
930 done
931
932 perl -w ${deb/deb-closure.pl} \
933 ./Packages ${urlPrefix} ${toString packages} > $out
934 nixfmt $out
935 '';
936
937 /*
938 Helper function that combines debClosureGenerator and
939 fillDiskWithDebs to generate a disk image from a set of package
940 names.
941 */
942
943 makeImageFromDebDist =
944 {
945 name,
946 fullName,
947 size ? 4096,
948 urlPrefix,
949 packagesList ? "",
950 packagesLists ? [ packagesList ],
951 packages,
952 extraPackages ? [ ],
953 postInstall ? "",
954 extraDebs ? [ ],
955 createRootFS ? defaultCreateRootFS,
956 QEMU_OPTS ? "",
957 memSize ? 512,
958 ...
959 }@args:
960
961 let
962 expr = debClosureGenerator {
963 inherit name packagesLists urlPrefix;
964 packages = packages ++ extraPackages;
965 };
966 in
967 (fillDiskWithDebs (
968 {
969 inherit
970 name
971 fullName
972 size
973 postInstall
974 createRootFS
975 QEMU_OPTS
976 memSize
977 ;
978 debs = import expr { inherit fetchurl; } ++ extraDebs;
979 }
980 // args
981 ))
982 // {
983 inherit expr;
984 };
985
986 # The set of supported RPM-based distributions.
987
988 rpmDistros = { };
989
990 # The set of supported Dpkg-based distributions.
991
992 debDistros = {
993 ubuntu2204i386 = {
994 name = "ubuntu-22.04-jammy-i386";
995 fullName = "Ubuntu 22.04 Jammy (i386)";
996 packagesLists = [
997 (fetchurl {
998 url = "mirror://ubuntu/dists/jammy/main/binary-i386/Packages.xz";
999 sha256 = "sha256-iZBmwT0ep4v+V3sayybbOgZBOFFZwPGpOKtmuLMMVPQ=";
1000 })
1001 (fetchurl {
1002 url = "mirror://ubuntu/dists/jammy/universe/binary-i386/Packages.xz";
1003 sha256 = "sha256-DO2LdpZ9rDDBhWj2gvDWd0TJJVZHxKsYTKTi6GXjm1E=";
1004 })
1005 ];
1006 urlPrefix = "mirror://ubuntu";
1007 packages = commonDebPackages ++ [
1008 "diffutils"
1009 "libc-bin"
1010 ];
1011 };
1012
1013 ubuntu2204x86_64 = {
1014 name = "ubuntu-22.04-jammy-amd64";
1015 fullName = "Ubuntu 22.04 Jammy (amd64)";
1016 packagesLists = [
1017 (fetchurl {
1018 url = "mirror://ubuntu/dists/jammy/main/binary-amd64/Packages.xz";
1019 sha256 = "sha256-N8tX8VVMv6ccWinun/7hipqMF4K7BWjgh0t/9M6PnBE=";
1020 })
1021 (fetchurl {
1022 url = "mirror://ubuntu/dists/jammy/universe/binary-amd64/Packages.xz";
1023 sha256 = "sha256-0pyyTJP+xfQyVXBrzn60bUd5lSA52MaKwbsUpvNlXOI=";
1024 })
1025 ];
1026 urlPrefix = "mirror://ubuntu";
1027 packages = commonDebPackages ++ [
1028 "diffutils"
1029 "libc-bin"
1030 ];
1031 };
1032
1033 ubuntu2404x86_64 = {
1034 name = "ubuntu-24.04-noble-amd64";
1035 fullName = "Ubuntu 24.04 Noble (amd64)";
1036 packagesLists = [
1037 (fetchurl {
1038 url = "mirror://ubuntu/dists/noble/main/binary-amd64/Packages.xz";
1039 sha256 = "sha256-KmoZnhAxpcJ5yzRmRtWUmT81scA91KgqqgMjmA3ZJFE=";
1040 })
1041 (fetchurl {
1042 url = "mirror://ubuntu/dists/noble/universe/binary-amd64/Packages.xz";
1043 sha256 = "sha256-upBX+huRQ4zIodJoCNAMhTif4QHQwUliVN+XI2QFWZo=";
1044 })
1045 ];
1046 urlPrefix = "mirror://ubuntu";
1047 packages = commonDebPackages ++ [
1048 "diffutils"
1049 "libc-bin"
1050 ];
1051 };
1052
1053 debian11i386 = {
1054 name = "debian-11.8-bullseye-i386";
1055 fullName = "Debian 11.8 Bullseye (i386)";
1056 packagesList = fetchurl {
1057 url = "https://snapshot.debian.org/archive/debian/20231124T031419Z/dists/bullseye/main/binary-i386/Packages.xz";
1058 hash = "sha256-0bKSLLPhEC7FB5D1NA2jaQP0wTe/Qp1ddiA/NDVjRaI=";
1059 };
1060 urlPrefix = "https://snapshot.debian.org/archive/debian/20231124T031419Z";
1061 packages = commonDebianPackages;
1062 };
1063
1064 debian11x86_64 = {
1065 name = "debian-11.8-bullseye-amd64";
1066 fullName = "Debian 11.8 Bullseye (amd64)";
1067 packagesList = fetchurl {
1068 url = "https://snapshot.debian.org/archive/debian/20231124T031419Z/dists/bullseye/main/binary-amd64/Packages.xz";
1069 hash = "sha256-CYPsGgQgJZkh3JmbcAQkYDWP193qrkOADOgrMETZIeo=";
1070 };
1071 urlPrefix = "https://snapshot.debian.org/archive/debian/20231124T031419Z";
1072 packages = commonDebianPackages;
1073 };
1074
1075 debian12i386 = {
1076 name = "debian-12.2-bookworm-i386";
1077 fullName = "Debian 12.2 Bookworm (i386)";
1078 packagesList = fetchurl {
1079 url = "https://snapshot.debian.org/archive/debian/20231124T031419Z/dists/bookworm/main/binary-i386/Packages.xz";
1080 hash = "sha256-OeN9Q2HFM3GsPNhOa4VhM7qpwT66yUNwC+6Z8SbGEeQ=";
1081 };
1082 urlPrefix = "https://snapshot.debian.org/archive/debian/20231124T031419Z";
1083 packages = commonDebianPackages;
1084 };
1085
1086 debian12x86_64 = {
1087 name = "debian-12.2-bookworm-amd64";
1088 fullName = "Debian 12.2 Bookworm (amd64)";
1089 packagesList = fetchurl {
1090 url = "https://snapshot.debian.org/archive/debian/20231124T031419Z/dists/bookworm/main/binary-amd64/Packages.xz";
1091 hash = "sha256-SZDElRfe9BlBwDlajQB79Qdn08rv8whYoQDeVCveKVs=";
1092 };
1093 urlPrefix = "https://snapshot.debian.org/archive/debian/20231124T031419Z";
1094 packages = commonDebianPackages;
1095 };
1096
1097 debian13i386 = {
1098 name = "debian-13.0-trixie-i386";
1099 fullName = "Debian 13.0 Trixie (i386)";
1100 packagesList = fetchurl {
1101 url = "https://snapshot.debian.org/archive/debian/20250819T202603Z/dists/trixie/main/binary-i386/Packages.xz";
1102 hash = "sha256-fXjhaG1Y+kn6iMEtqVZLwYN7lZ0cEQKVfMS3hSHJipY=";
1103 };
1104 urlPrefix = "https://snapshot.debian.org/archive/debian/20250819T202603Z";
1105 packages = commonDebianPackages;
1106 };
1107
1108 debian13x86_64 = {
1109 name = "debian-13.0-trixie-amd64";
1110 fullName = "Debian 13.0 Trixie (amd64)";
1111 packagesList = fetchurl {
1112 url = "https://snapshot.debian.org/archive/debian/20250819T202603Z/dists/trixie/main/binary-amd64/Packages.xz";
1113 hash = "sha256-15cDoCcTv3m5fiZqP1hqWWnSG1BVUZSrm5YszTSKQs4=";
1114 };
1115 urlPrefix = "https://snapshot.debian.org/archive/debian/20250819T202603Z";
1116 packages = commonDebianPackages;
1117 };
1118 };
1119
1120 # Common packages for Fedora images.
1121 commonFedoraPackages = [
1122 "autoconf"
1123 "automake"
1124 "basesystem"
1125 "bzip2"
1126 "curl"
1127 "diffutils"
1128 "fedora-release"
1129 "findutils"
1130 "gawk"
1131 "gcc-c++"
1132 "gzip"
1133 "make"
1134 "patch"
1135 "perl"
1136 "pkgconf-pkg-config"
1137 "rpm"
1138 "rpm-build"
1139 "tar"
1140 "unzip"
1141 ];
1142
1143 commonCentOSPackages = [
1144 "autoconf"
1145 "automake"
1146 "basesystem"
1147 "bzip2"
1148 "curl"
1149 "diffutils"
1150 "centos-release"
1151 "findutils"
1152 "gawk"
1153 "gcc-c++"
1154 "gzip"
1155 "make"
1156 "patch"
1157 "perl"
1158 "pkgconfig"
1159 "rpm"
1160 "rpm-build"
1161 "tar"
1162 "unzip"
1163 ];
1164
1165 commonRHELPackages = [
1166 "autoconf"
1167 "automake"
1168 "basesystem"
1169 "bzip2"
1170 "curl"
1171 "diffutils"
1172 "findutils"
1173 "gawk"
1174 "gcc-c++"
1175 "gzip"
1176 "make"
1177 "patch"
1178 "perl"
1179 "pkgconfig"
1180 "procps-ng"
1181 "rpm"
1182 "rpm-build"
1183 "tar"
1184 "unzip"
1185 ];
1186
1187 # Common packages for openSUSE images.
1188 commonOpenSUSEPackages = [
1189 "aaa_base"
1190 "autoconf"
1191 "automake"
1192 "bzip2"
1193 "curl"
1194 "diffutils"
1195 "findutils"
1196 "gawk"
1197 "gcc-c++"
1198 "gzip"
1199 "make"
1200 "patch"
1201 "perl"
1202 "pkg-config"
1203 "rpm"
1204 "tar"
1205 "unzip"
1206 "util-linux"
1207 "gnu-getopt"
1208 ];
1209
1210 # Common packages for Debian/Ubuntu images.
1211 commonDebPackages = [
1212 "base-passwd"
1213 "dpkg"
1214 "libc6-dev"
1215 "perl"
1216 "bash"
1217 "dash"
1218 "gzip"
1219 "bzip2"
1220 "tar"
1221 "grep"
1222 "mawk"
1223 "sed"
1224 "findutils"
1225 "g++"
1226 "make"
1227 "curl"
1228 "patch"
1229 "locales"
1230 "coreutils"
1231 # Needed by checkinstall:
1232 "util-linux"
1233 "file"
1234 "dpkg-dev"
1235 "pkg-config"
1236 # Needed because it provides /etc/login.defs, whose absence causes
1237 # the "passwd" post-installs script to fail.
1238 "login"
1239 "passwd"
1240 ];
1241
1242 commonDebianPackages = commonDebPackages ++ [
1243 "sysvinit"
1244 "diff"
1245 ];
1246
1247 /*
1248 A set of functions that build the Linux distributions specified
1249 in `rpmDistros' and `debDistros'. For instance,
1250 `diskImageFuns.ubuntu1004x86_64 { }' builds an Ubuntu 10.04 disk
1251 image containing the default packages specified above. Overrides
1252 of the default image parameters can be given. In particular,
1253 `extraPackages' specifies the names of additional packages from
1254 the distribution that should be included in the image; `packages'
1255 allows the entire set of packages to be overridden; and `size'
1256 sets the size of the disk in MiB (1024*1024 bytes). E.g.,
1257 `diskImageFuns.ubuntu1004x86_64 { extraPackages = ["firefox"];
1258 size = 8192; }' builds an 8 GiB image containing Firefox in
1259 addition to the default packages.
1260 */
1261 diskImageFuns =
1262 (lib.mapAttrs (
1263 name: as: as2:
1264 makeImageFromRPMDist (as // as2)
1265 ) rpmDistros)
1266 // (lib.mapAttrs (
1267 name: as: as2:
1268 makeImageFromDebDist (as // as2)
1269 ) debDistros);
1270
1271 # Shorthand for `diskImageFuns.<attr> { extraPackages = ... }'.
1272 diskImageExtraFuns = lib.mapAttrs (
1273 name: f: extraPackages:
1274 f { inherit extraPackages; }
1275 ) diskImageFuns;
1276
1277 /*
1278 Default disk images generated from the `rpmDistros' and
1279 `debDistros' sets.
1280 */
1281 diskImages = lib.mapAttrs (name: f: f { }) diskImageFuns;
1282
1283}