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