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