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}