diff options
author | Alyssa Ross <hi@alyssa.is> | 2019-01-07 02:18:36 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2019-01-07 02:18:47 +0000 |
commit | 36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2 (patch) | |
tree | b3faaf573407b32aa645237a4d16b82778a39a92 /nixpkgs/pkgs/build-support/vm | |
parent | 4e31070265257dc67d120c27e0f75c2344fdfa9a (diff) | |
parent | abf060725d7614bd3b9f96764262dfbc2f9c2199 (diff) | |
download | nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.gz nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.bz2 nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.lz nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.xz nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.zst nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.zip |
Add 'nixpkgs/' from commit 'abf060725d7614bd3b9f96764262dfbc2f9c2199'
git-subtree-dir: nixpkgs git-subtree-mainline: 4e31070265257dc67d120c27e0f75c2344fdfa9a git-subtree-split: abf060725d7614bd3b9f96764262dfbc2f9c2199
Diffstat (limited to 'nixpkgs/pkgs/build-support/vm')
-rw-r--r-- | nixpkgs/pkgs/build-support/vm/deb/deb-closure.pl | 180 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/vm/default.nix | 1167 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/vm/rpm/rpm-closure.pl | 184 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/vm/test.nix | 39 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/vm/windows/bootstrap.nix | 83 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/vm/windows/controller/default.nix | 262 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/default.nix | 56 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/mkclosure.py | 78 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/vm/windows/default.nix | 45 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/vm/windows/install/default.nix | 74 | ||||
-rw-r--r-- | nixpkgs/pkgs/build-support/vm/windows/install/unattended-image.nix | 123 |
11 files changed, 2291 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/build-support/vm/deb/deb-closure.pl b/nixpkgs/pkgs/build-support/vm/deb/deb-closure.pl new file mode 100644 index 000000000000..bed397d6f07e --- /dev/null +++ b/nixpkgs/pkgs/build-support/vm/deb/deb-closure.pl @@ -0,0 +1,180 @@ +use strict; +use Dpkg::Control; +use Dpkg::Deps; +use File::Basename; + +my $packagesFile = shift @ARGV; +my $urlPrefix = shift @ARGV; +my @toplevelPkgs = @ARGV; + + +my %packages; + + +# Parse the Packages file. +open PACKAGES, "<$packagesFile" or die; + +while (1) { + my $cdata = Dpkg::Control->new(type => CTRL_INFO_PKG); + last if not $cdata->parse(\*PACKAGES, $packagesFile); + die unless defined $cdata->{Package}; + #print STDERR $cdata->{Package}, "\n"; + $packages{$cdata->{Package}} = $cdata; +} + +close PACKAGES; + + +# Flatten a Dpkg::Deps dependency value into a list of package names. +sub getDeps { + my $deps = shift; + #print "$deps\n"; + if ($deps->isa('Dpkg::Deps::AND')) { + my @res = (); + foreach my $dep ($deps->get_deps()) { + push @res, getDeps($dep); + } + return @res; + } elsif ($deps->isa('Dpkg::Deps::OR')) { + # Arbitrarily pick the first alternative. + return getDeps(($deps->get_deps())[0]); + } elsif ($deps->isa('Dpkg::Deps::Simple')) { + return ($deps->{package}); + } else { + die "unknown dep type"; + } +} + + +# Process the "Provides" and "Replaces" fields to be able to resolve +# virtual dependencies. +my %provides; + +foreach my $cdata (values %packages) { + if (defined $cdata->{Provides}) { + my @provides = getDeps(Dpkg::Deps::deps_parse($cdata->{Provides})); + foreach my $name (@provides) { + #die "conflicting provide: $name\n" if defined $provides{$name}; + #warn "provide by $cdata->{Package} conflicts with package with the same name: $name\n"; + next if defined $packages{$name}; + $provides{$name} = $cdata->{Package}; + } + } + # Treat "Replaces" like "Provides". + if (defined $cdata->{Replaces}) { + my @replaces = getDeps(Dpkg::Deps::deps_parse($cdata->{Replaces})); + foreach my $name (@replaces) { + next if defined $packages{$name}; + $provides{$name} = $cdata->{Package}; + } + } +} + + +# Determine the closure of a package. +my %donePkgs; +my %depsUsed; +my @order = (); + +sub closePackage { + my $pkgName = shift; + print STDERR ">>> $pkgName\n"; + my $cdata = $packages{$pkgName}; + + if (!defined $cdata) { + die "unknown (virtual) package $pkgName" + unless defined $provides{$pkgName}; + print STDERR "virtual $pkgName: using $provides{$pkgName}\n"; + $pkgName = $provides{$pkgName}; + $cdata = $packages{$pkgName}; + } + + die "unknown package $pkgName" unless defined $cdata; + return if defined $donePkgs{$pkgName}; + $donePkgs{$pkgName} = 1; + + if (defined $cdata->{Provides}) { + foreach my $name (getDeps(Dpkg::Deps::deps_parse($cdata->{Provides}))) { + $provides{$name} = $cdata->{Package}; + } + } + + my @depNames = (); + + if (defined $cdata->{Depends}) { + print STDERR " $pkgName: $cdata->{Depends}\n"; + my $deps = Dpkg::Deps::deps_parse($cdata->{Depends}); + die unless defined $deps; + push @depNames, getDeps($deps); + } + + if (defined $cdata->{'Pre-Depends'}) { + print STDERR " $pkgName: $cdata->{'Pre-Depends'}\n"; + my $deps = Dpkg::Deps::deps_parse($cdata->{'Pre-Depends'}); + die unless defined $deps; + push @depNames, getDeps($deps); + } + + foreach my $depName (@depNames) { + closePackage($depName); + } + + push @order, $pkgName; + $depsUsed{$pkgName} = \@depNames; +} + +foreach my $pkgName (@toplevelPkgs) { + closePackage $pkgName; +} + + +# Generate the output Nix expression. +print "# This is a generated file. Do not modify!\n"; +print "# Following are the Debian packages constituting the closure of: @toplevelPkgs\n\n"; +print "{fetchurl}:\n\n"; +print "[\n\n"; + +# Output the packages in strongly connected components. +my %done; +my %forward; +my $newComponent = 1; +foreach my $pkgName (@order) { + $done{$pkgName} = 1; + my $cdata = $packages{$pkgName}; + my @deps = @{$depsUsed{$pkgName}}; + foreach my $dep (@deps) { + $dep = $provides{$dep} if defined $provides{$dep}; + $forward{$dep} = 1 unless defined $done{$dep}; + } + delete $forward{$pkgName}; + + print " [\n\n" if $newComponent; + $newComponent = 0; + + my $origName = basename $cdata->{Filename}; + my $cleanedName = $origName; + $cleanedName =~ s/~//g; + + print " (fetchurl {\n"; + print " url = $urlPrefix/$cdata->{Filename};\n"; + print " sha256 = \"$cdata->{SHA256}\";\n"; + print " name = \"$cleanedName\";\n" if $cleanedName ne $origName; + print " })\n"; + print "\n"; + + if (keys %forward == 0) { + print " ]\n\n"; + $newComponent = 1; + } +} + +foreach my $pkgName (@order) { + my $cdata = $packages{$pkgName}; +} + +print "]\n"; + +if ($newComponent != 1) { + print STDERR "argh: ", keys %forward, "\n"; + exit 1; +} diff --git a/nixpkgs/pkgs/build-support/vm/default.nix b/nixpkgs/pkgs/build-support/vm/default.nix new file mode 100644 index 000000000000..7880d98e6b6a --- /dev/null +++ b/nixpkgs/pkgs/build-support/vm/default.nix @@ -0,0 +1,1167 @@ +{ pkgs +, kernel ? pkgs.linux +, img ? pkgs.stdenv.hostPlatform.platform.kernelTarget +, storeDir ? builtins.storeDir +, rootModules ? + [ "virtio_pci" "virtio_mmio" "virtio_blk" "virtio_balloon" "virtio_rng" "ext4" "unix" "9p" "9pnet_virtio" "crc32c_generic" ] + ++ pkgs.lib.optional (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) "rtc_cmos" +}: + +with pkgs; +with import ../../../nixos/lib/qemu-flags.nix { inherit pkgs; }; + +rec { + + qemu = pkgs.qemu_kvm; + + modulesClosure = makeModulesClosure { + inherit kernel rootModules; + firmware = kernel; + }; + + + hd = "vda"; # either "sda" or "vda" + + initrdUtils = runCommand "initrd-utils" + { buildInputs = [ nukeReferences ]; + allowedReferences = [ "out" modulesClosure ]; # prevent accidents like glibc being included in the initrd + } + '' + mkdir -p $out/bin + mkdir -p $out/lib + + # Copy what we need from Glibc. + cp -p ${pkgs.stdenv.glibc.out}/lib/ld-linux*.so.? $out/lib + cp -p ${pkgs.stdenv.glibc.out}/lib/libc.so.* $out/lib + cp -p ${pkgs.stdenv.glibc.out}/lib/libm.so.* $out/lib + cp -p ${pkgs.stdenv.glibc.out}/lib/libresolv.so.* $out/lib + + # Copy BusyBox. + cp -pd ${pkgs.busybox}/bin/* $out/bin + + # Run patchelf to make the programs refer to the copied libraries. + for i in $out/bin/* $out/lib/*; do if ! test -L $i; then nuke-refs $i; fi; done + + for i in $out/bin/*; do + if [ -f "$i" -a ! -L "$i" ]; then + echo "patching $i..." + patchelf --set-interpreter $out/lib/ld-linux*.so.? --set-rpath $out/lib $i || true + fi + done + ''; # */ + + + stage1Init = writeScript "vm-run-stage1" '' + #! ${initrdUtils}/bin/ash -e + + export PATH=${initrdUtils}/bin + + mkdir /etc + echo -n > /etc/fstab + + mount -t proc none /proc + mount -t sysfs none /sys + + echo 2 > /proc/sys/vm/panic_on_oom + + for o in $(cat /proc/cmdline); do + case $o in + mountDisk=1) + mountDisk=1 + ;; + command=*) + set -- $(IFS==; echo $o) + command=$2 + ;; + out=*) + set -- $(IFS==; echo $o) + export out=$2 + ;; + esac + done + + echo "loading kernel modules..." + for i in $(cat ${modulesClosure}/insmod-list); do + insmod $i || echo "warning: unable to load $i" + done + + mount -t devtmpfs devtmpfs /dev + + ifconfig lo up + + mkdir /fs + + if test -z "$mountDisk"; then + mount -t tmpfs none /fs + else + mount /dev/${hd} /fs + fi + + mkdir -p /fs/dev + mount -o bind /dev /fs/dev + + mkdir -p /fs/dev/shm /fs/dev/pts + mount -t tmpfs -o "mode=1777" none /fs/dev/shm + mount -t devpts none /fs/dev/pts + + echo "mounting Nix store..." + mkdir -p /fs${storeDir} + mount -t 9p store /fs${storeDir} -o trans=virtio,version=9p2000.L,cache=loose + + mkdir -p /fs/tmp /fs/run /fs/var + mount -t tmpfs -o "mode=1777" none /fs/tmp + mount -t tmpfs -o "mode=755" none /fs/run + ln -sfn /run /fs/var/run + + echo "mounting host's temporary directory..." + mkdir -p /fs/tmp/xchg + mount -t 9p xchg /fs/tmp/xchg -o trans=virtio,version=9p2000.L,cache=loose + + mkdir -p /fs/proc + mount -t proc none /fs/proc + + mkdir -p /fs/sys + mount -t sysfs none /fs/sys + + mkdir -p /fs/etc + ln -sf /proc/mounts /fs/etc/mtab + echo "127.0.0.1 localhost" > /fs/etc/hosts + + echo "starting stage 2 ($command)" + exec switch_root /fs $command $out + ''; + + + initrd = makeInitrd { + contents = [ + { object = stage1Init; + symlink = "/init"; + } + ]; + }; + + + stage2Init = writeScript "vm-run-stage2" '' + #! ${bash}/bin/sh + source /tmp/xchg/saved-env + + # Set the system time from the hardware clock. Works around an + # apparent KVM > 1.5.2 bug. + ${pkgs.utillinux}/bin/hwclock -s + + export NIX_STORE=${storeDir} + export NIX_BUILD_TOP=/tmp + export TMPDIR=/tmp + export PATH=/empty + out="$1" + cd "$NIX_BUILD_TOP" + + if ! test -e /bin/sh; then + ${coreutils}/bin/mkdir -p /bin + ${coreutils}/bin/ln -s ${bash}/bin/sh /bin/sh + fi + + # Set up automatic kernel module loading. + export MODULE_DIR=${linux}/lib/modules/ + ${coreutils}/bin/cat <<EOF > /run/modprobe + #! /bin/sh + export MODULE_DIR=$MODULE_DIR + exec ${kmod}/bin/modprobe "\$@" + EOF + ${coreutils}/bin/chmod 755 /run/modprobe + echo /run/modprobe > /proc/sys/kernel/modprobe + + # For debugging: if this is the second time this image is run, + # then don't start the build again, but instead drop the user into + # an interactive shell. + if test -n "$origBuilder" -a ! -e /.debug; then + exec < /dev/null + ${coreutils}/bin/touch /.debug + $origBuilder $origArgs + echo $? > /tmp/xchg/in-vm-exit + + ${busybox}/bin/mount -o remount,ro dummy / + + ${busybox}/bin/poweroff -f + else + export PATH=/bin:/usr/bin:${coreutils}/bin + echo "Starting interactive shell..." + echo "(To run the original builder: \$origBuilder \$origArgs)" + exec ${busybox}/bin/setsid ${bashInteractive}/bin/bash < /dev/${qemuSerialDevice} &> /dev/${qemuSerialDevice} + fi + ''; + + + qemuCommandLinux = '' + ${qemuBinary qemu} \ + -nographic -no-reboot \ + -device virtio-rng-pci \ + -virtfs local,path=${storeDir},security_model=none,mount_tag=store \ + -virtfs local,path=$TMPDIR/xchg,security_model=none,mount_tag=xchg \ + ''${diskImage:+-drive file=$diskImage,if=virtio,cache=unsafe,werror=report} \ + -kernel ${kernel}/${img} \ + -initrd ${initrd}/initrd \ + -append "console=${qemuSerialDevice} panic=1 command=${stage2Init} out=$out mountDisk=$mountDisk loglevel=4" \ + $QEMU_OPTS + ''; + + + vmRunCommand = qemuCommand: writeText "vm-run" '' + export > saved-env + + PATH=${coreutils}/bin + mkdir xchg + mv saved-env xchg/ + + eval "$preVM" + + if [ "$enableParallelBuilding" = 1 ]; then + if [ ''${NIX_BUILD_CORES:-0} = 0 ]; then + QEMU_OPTS+=" -smp cpus=$(nproc)" + else + QEMU_OPTS+=" -smp cpus=$NIX_BUILD_CORES" + fi + fi + + # Write the command to start the VM to a file so that the user can + # debug inside the VM if the build fails (when Nix is called with + # the -K option to preserve the temporary build directory). + cat > ./run-vm <<EOF + #! ${bash}/bin/sh + ''${diskImage:+diskImage=$diskImage} + TMPDIR=$TMPDIR + cd $TMPDIR + ${qemuCommand} + EOF + + mkdir -p -m 0700 $out + + chmod +x ./run-vm + source ./run-vm + + if ! test -e xchg/in-vm-exit; then + echo "Virtual machine didn't produce an exit code." + exit 1 + fi + + exitCode="$(cat xchg/in-vm-exit)" + if [ "$exitCode" != "0" ]; then + exit "$exitCode" + fi + + eval "$postVM" + ''; + + + createEmptyImage = {size, fullName}: '' + mkdir $out + diskImage=$out/disk-image.qcow2 + ${qemu}/bin/qemu-img create -f qcow2 $diskImage "${toString size}M" + + mkdir $out/nix-support + echo "${fullName}" > $out/nix-support/full-name + ''; + + + defaultCreateRootFS = '' + mkdir /mnt + ${e2fsprogs}/bin/mkfs.ext4 /dev/${hd} + ${utillinux}/bin/mount -t ext4 /dev/${hd} /mnt + + if test -e /mnt/.debug; then + exec ${bash}/bin/sh + fi + touch /mnt/.debug + + mkdir /mnt/proc /mnt/dev /mnt/sys + ''; + + + /* Run a derivation in a Linux virtual machine (using Qemu/KVM). By + default, there is no disk image; the root filesystem is a tmpfs, + and the nix store is shared with the host (via the 9P protocol). + Thus, any pure Nix derivation should run unmodified, e.g. the + call + + runInLinuxVM patchelf + + will build the derivation `patchelf' inside a VM. The attribute + `preVM' can optionally contain a shell command to be evaluated + *before* the VM is started (i.e., on the host). The attribute + `memSize' specifies the memory size of the VM in megabytes, + defaulting to 512. The attribute `diskImage' can optionally + specify a file system image to be attached to /dev/sda. (Note + that currently we expect the image to contain a filesystem, not a + full disk image with a partition table etc.) + + If the build fails and Nix is run with the `-K' option, a script + `run-vm' will be left behind in the temporary build directory + that allows you to boot into the VM and debug it interactively. */ + + runInLinuxVM = drv: lib.overrideDerivation drv ({ memSize ? 512, QEMU_OPTS ? "", args, builder, ... }: { + requiredSystemFeatures = [ "kvm" ]; + builder = "${bash}/bin/sh"; + args = ["-e" (vmRunCommand qemuCommandLinux)]; + origArgs = args; + origBuilder = builder; + QEMU_OPTS = "${QEMU_OPTS} -m ${toString memSize}"; + passAsFile = []; # HACK fix - see https://github.com/NixOS/nixpkgs/issues/16742 + }); + + + extractFs = {file, fs ? null} : + with pkgs; runInLinuxVM ( + stdenv.mkDerivation { + name = "extract-file"; + buildInputs = [ utillinux ]; + buildCommand = '' + ln -s ${linux}/lib /lib + ${kmod}/bin/modprobe loop + ${kmod}/bin/modprobe ext4 + ${kmod}/bin/modprobe hfs + ${kmod}/bin/modprobe hfsplus + ${kmod}/bin/modprobe squashfs + ${kmod}/bin/modprobe iso9660 + ${kmod}/bin/modprobe ufs + ${kmod}/bin/modprobe cramfs + + mkdir -p $out + mkdir -p tmp + mount -o loop,ro,ufstype=44bsd ${lib.optionalString (fs != null) "-t ${fs} "}${file} tmp || + mount -o loop,ro ${lib.optionalString (fs != null) "-t ${fs} "}${file} tmp + cp -Rv tmp/* $out/ || exit 0 + ''; + }); + + + extractMTDfs = {file, fs ? null} : + with pkgs; runInLinuxVM ( + stdenv.mkDerivation { + name = "extract-file-mtd"; + buildInputs = [ utillinux mtdutils ]; + buildCommand = '' + ln -s ${linux}/lib /lib + ${kmod}/bin/modprobe mtd + ${kmod}/bin/modprobe mtdram total_size=131072 + ${kmod}/bin/modprobe mtdchar + ${kmod}/bin/modprobe mtdblock + ${kmod}/bin/modprobe jffs2 + ${kmod}/bin/modprobe zlib + + mkdir -p $out + mkdir -p tmp + + dd if=${file} of=/dev/mtd0 + mount ${lib.optionalString (fs != null) "-t ${fs} "}/dev/mtdblock0 tmp + + cp -R tmp/* $out/ + ''; + }); + + + /* Like runInLinuxVM, but run the build not using the stdenv from + the Nix store, but using the tools provided by /bin, /usr/bin + etc. from the specified filesystem image, which typically is a + filesystem containing a non-NixOS Linux distribution. */ + + runInLinuxImage = drv: runInLinuxVM (lib.overrideDerivation drv (attrs: { + mountDisk = true; + + /* Mount `image' as the root FS, but use a temporary copy-on-write + image since we don't want to (and can't) write to `image'. */ + preVM = '' + diskImage=$(pwd)/disk-image.qcow2 + origImage=${attrs.diskImage} + if test -d "$origImage"; then origImage="$origImage/disk-image.qcow2"; fi + ${qemu}/bin/qemu-img create -b "$origImage" -f qcow2 $diskImage + ''; + + /* Inside the VM, run the stdenv setup script normally, but at the + very end set $PATH and $SHELL to the `native' paths for the + distribution inside the VM. */ + postHook = '' + PATH=/usr/bin:/bin:/usr/sbin:/sbin + SHELL=/bin/sh + eval "$origPostHook" + ''; + + origPostHook = if attrs ? postHook then attrs.postHook else ""; + + /* Don't run Nix-specific build steps like patchelf. */ + fixupPhase = "true"; + })); + + + /* Create a filesystem image of the specified size and fill it with + a set of RPM packages. */ + + fillDiskWithRPMs = + { size ? 4096, rpms, name, fullName, preInstall ? "", postInstall ? "" + , runScripts ? true, createRootFS ? defaultCreateRootFS + , QEMU_OPTS ? "", memSize ? 512 + , unifiedSystemDir ? false + }: + + runInLinuxVM (stdenv.mkDerivation { + inherit name preInstall postInstall rpms QEMU_OPTS memSize; + preVM = createEmptyImage {inherit size fullName;}; + + buildCommand = '' + ${createRootFS} + + chroot=$(type -tP chroot) + + # Make the Nix store available in /mnt, because that's where the RPMs live. + mkdir -p /mnt${storeDir} + ${utillinux}/bin/mount -o bind ${storeDir} /mnt${storeDir} + + # Newer distributions like Fedora 18 require /lib etc. to be + # symlinked to /usr. + ${lib.optionalString unifiedSystemDir '' + mkdir -p /mnt/usr/bin /mnt/usr/sbin /mnt/usr/lib /mnt/usr/lib64 + ln -s /usr/bin /mnt/bin + ln -s /usr/sbin /mnt/sbin + ln -s /usr/lib /mnt/lib + ln -s /usr/lib64 /mnt/lib64 + ${utillinux}/bin/mount -t proc none /mnt/proc + ''} + + echo "unpacking RPMs..." + set +o pipefail + for i in $rpms; do + echo "$i..." + ${rpm}/bin/rpm2cpio "$i" | chroot /mnt ${cpio}/bin/cpio -i --make-directories --unconditional --extract-over-symlinks + done + + eval "$preInstall" + + echo "initialising RPM DB..." + PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \ + ldconfig -v || true + PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \ + rpm --initdb + + ${utillinux}/bin/mount -o bind /tmp /mnt/tmp + + echo "installing RPMs..." + PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \ + rpm -iv --nosignature ${if runScripts then "" else "--noscripts"} $rpms + + echo "running post-install script..." + eval "$postInstall" + + rm /mnt/.debug + + ${utillinux}/bin/umount /mnt${storeDir} /mnt/tmp ${lib.optionalString unifiedSystemDir "/mnt/proc"} + ${utillinux}/bin/umount /mnt + ''; + + passthru = { inherit fullName; }; + }); + + + /* Generate a script that can be used to run an interactive session + in the given image. */ + + makeImageTestScript = image: writeScript "image-test" '' + #! ${bash}/bin/sh + if test -z "$1"; then + echo "Syntax: $0 <copy-on-write-temp-file>" + exit 1 + fi + diskImage="$1" + if ! test -e "$diskImage"; then + ${qemu}/bin/qemu-img create -b ${image}/disk-image.qcow2 -f qcow2 "$diskImage" + fi + export TMPDIR=$(mktemp -d) + export out=/dummy + export origBuilder= + export origArgs= + mkdir $TMPDIR/xchg + export > $TMPDIR/xchg/saved-env + mountDisk=1 + ${qemuCommandLinux} + ''; + + + /* Build RPM packages from the tarball `src' in the Linux + distribution installed in the filesystem `diskImage'. The + tarball must contain an RPM specfile. */ + + buildRPM = attrs: runInLinuxImage (stdenv.mkDerivation ({ + phases = "prepareImagePhase sysInfoPhase buildPhase installPhase"; + + outDir = "rpms/${attrs.diskImage.name}"; + + prepareImagePhase = '' + if test -n "$extraRPMs"; then + for rpmdir in $extraRPMs ; do + rpm -iv $(ls $rpmdir/rpms/*/*.rpm | grep -v 'src\.rpm' | sort | head -1) + done + fi + ''; + + sysInfoPhase = '' + echo "System/kernel: $(uname -a)" + if test -e /etc/fedora-release; then echo "Fedora release: $(cat /etc/fedora-release)"; fi + if test -e /etc/SuSE-release; then echo "SUSE release: $(cat /etc/SuSE-release)"; fi + header "installed RPM packages" + rpm -qa --qf "%{Name}-%{Version}-%{Release} (%{Arch}; %{Distribution}; %{Vendor})\n" + stopNest + ''; + + buildPhase = '' + eval "$preBuild" + + # Hacky: RPM looks for <basename>.spec inside the tarball, so + # strip off the hash. + srcName="$(stripHash "$src")" + cp "$src" "$srcName" # `ln' doesn't work always work: RPM requires that the file is owned by root + + export HOME=/tmp/home + mkdir $HOME + + rpmout=/tmp/rpmout + mkdir $rpmout $rpmout/SPECS $rpmout/BUILD $rpmout/RPMS $rpmout/SRPMS + + echo "%_topdir $rpmout" >> $HOME/.rpmmacros + + if [ `uname -m` = i686 ]; then extra="--target i686-linux"; fi + rpmbuild -vv $extra -ta "$srcName" + + eval "$postBuild" + ''; + + installPhase = '' + eval "$preInstall" + + mkdir -p $out/$outDir + find $rpmout -name "*.rpm" -exec cp {} $out/$outDir \; + + for i in $out/$outDir/*.rpm; do + header "Generated RPM/SRPM: $i" + rpm -qip $i + stopNest + done + + eval "$postInstall" + ''; # */ + } // attrs)); + + + /* Create a filesystem image of the specified size and fill it with + a set of Debian packages. `debs' must be a list of list of + .deb files, namely, the Debian packages grouped together into + strongly connected components. See deb/deb-closure.nix. */ + + fillDiskWithDebs = + { size ? 4096, debs, name, fullName, postInstall ? null, createRootFS ? defaultCreateRootFS + , QEMU_OPTS ? "", memSize ? 512 }: + + runInLinuxVM (stdenv.mkDerivation { + inherit name postInstall QEMU_OPTS memSize; + + debs = (lib.intersperse "|" debs); + + preVM = createEmptyImage {inherit size fullName;}; + + buildCommand = '' + ${createRootFS} + + PATH=$PATH:${stdenv.lib.makeBinPath [ dpkg dpkg glibc lzma ]} + + # Unpack the .debs. We do this to prevent pre-install scripts + # (which have lots of circular dependencies) from barfing. + echo "unpacking Debs..." + + for deb in $debs; do + if test "$deb" != "|"; then + echo "$deb..." + dpkg-deb --extract "$deb" /mnt + fi + done + + # Make the Nix store available in /mnt, because that's where the .debs live. + mkdir -p /mnt/inst${storeDir} + ${utillinux}/bin/mount -o bind ${storeDir} /mnt/inst${storeDir} + ${utillinux}/bin/mount -o bind /proc /mnt/proc + ${utillinux}/bin/mount -o bind /dev /mnt/dev + + # Misc. files/directories assumed by various packages. + echo "initialising Dpkg DB..." + touch /mnt/etc/shells + touch /mnt/var/lib/dpkg/status + touch /mnt/var/lib/dpkg/available + touch /mnt/var/lib/dpkg/diversions + + # Now install the .debs. This is basically just to register + # them with dpkg and to make their pre/post-install scripts + # run. + echo "installing Debs..." + + export DEBIAN_FRONTEND=noninteractive + + oldIFS="$IFS" + IFS="|" + for component in $debs; do + IFS="$oldIFS" + echo + echo ">>> INSTALLING COMPONENT: $component" + debs= + for i in $component; do + debs="$debs /inst/$i"; + done + chroot=$(type -tP chroot) + + # Create a fake start-stop-daemon script, as done in debootstrap. + mv "/mnt/sbin/start-stop-daemon" "/mnt/sbin/start-stop-daemon.REAL" + echo "#!/bin/true" > "/mnt/sbin/start-stop-daemon" + chmod 755 "/mnt/sbin/start-stop-daemon" + + PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \ + /usr/bin/dpkg --install --force-all $debs < /dev/null || true + + # Move the real start-stop-daemon back into its place. + mv "/mnt/sbin/start-stop-daemon.REAL" "/mnt/sbin/start-stop-daemon" + done + + echo "running post-install script..." + eval "$postInstall" + ln -sf dash /mnt/bin/sh + + rm /mnt/.debug + + ${utillinux}/bin/umount /mnt/inst${storeDir} + ${utillinux}/bin/umount /mnt/proc + ${utillinux}/bin/umount /mnt/dev + ${utillinux}/bin/umount /mnt + ''; + + passthru = { inherit fullName; }; + }); + + + /* Generate a Nix expression containing fetchurl calls for the + closure of a set of top-level RPM packages from the + `primary.xml.gz' file of a Fedora or openSUSE distribution. */ + + rpmClosureGenerator = + {name, packagesLists, urlPrefixes, packages, archs ? []}: + assert (builtins.length packagesLists) == (builtins.length urlPrefixes); + runCommand "${name}.nix" {buildInputs = [perl perlPackages.XMLSimple]; inherit archs;} '' + ${lib.concatImapStrings (i: pl: '' + gunzip < ${pl} > ./packages_${toString i}.xml + '') packagesLists} + perl -w ${rpm/rpm-closure.pl} \ + ${lib.concatImapStrings (i: pl: "./packages_${toString i}.xml ${pl.snd} " ) (lib.zipLists packagesLists urlPrefixes)} \ + ${toString packages} > $out + ''; + + + /* Helper function that combines rpmClosureGenerator and + fillDiskWithRPMs to generate a disk image from a set of package + names. */ + + makeImageFromRPMDist = + { name, fullName, size ? 4096 + , urlPrefix ? "", urlPrefixes ? [urlPrefix] + , packagesList ? "", packagesLists ? [packagesList] + , packages, extraPackages ? [] + , preInstall ? "", postInstall ? "", archs ? ["noarch" "i386"] + , runScripts ? true, createRootFS ? defaultCreateRootFS + , QEMU_OPTS ? "", memSize ? 512 + , unifiedSystemDir ? false }: + + fillDiskWithRPMs { + inherit name fullName size preInstall postInstall runScripts createRootFS unifiedSystemDir QEMU_OPTS memSize; + rpms = import (rpmClosureGenerator { + inherit name packagesLists urlPrefixes archs; + packages = packages ++ extraPackages; + }) { inherit fetchurl; }; + }; + + + /* Like `rpmClosureGenerator', but now for Debian/Ubuntu releases + (i.e. generate a closure from a Packages.bz2 file). */ + + debClosureGenerator = + {name, packagesLists, urlPrefix, packages}: + + runCommand "${name}.nix" { buildInputs = [ perl dpkg ]; } '' + for i in ${toString packagesLists}; do + echo "adding $i..." + case $i in + *.xz | *.lzma) + xz -d < $i >> ./Packages + ;; + *.bz2) + bunzip2 < $i >> ./Packages + ;; + *.gz) + gzip -dc < $i >> ./Packages + ;; + esac + done + + # Work around this bug: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=452279 + sed -i ./Packages -e s/x86_64-linux-gnu/x86-64-linux-gnu/g + + perl -w ${deb/deb-closure.pl} \ + ./Packages ${urlPrefix} ${toString packages} > $out + ''; + + + /* Helper function that combines debClosureGenerator and + fillDiskWithDebs to generate a disk image from a set of package + names. */ + + makeImageFromDebDist = + { name, fullName, size ? 4096, urlPrefix + , packagesList ? "", packagesLists ? [packagesList] + , packages, extraPackages ? [], postInstall ? "" + , extraDebs ? [] + , QEMU_OPTS ? "", memSize ? 512 }: + + let + expr = debClosureGenerator { + inherit name packagesLists urlPrefix; + packages = packages ++ extraPackages; + }; + in + (fillDiskWithDebs { + inherit name fullName size postInstall QEMU_OPTS memSize; + debs = import expr {inherit fetchurl;} ++ extraDebs; + }) // {inherit expr;}; + + + /* The set of supported RPM-based distributions. */ + + rpmDistros = { + + # Note: no i386 release for Fedora >= 26 + fedora26x86_64 = + let version = "26"; + in rec { + name = "fedora-${version}-x86_64"; + fullName = "Fedora ${version} (x86_64)"; + packagesList = fetchurl rec { + url = "mirror://fedora/linux/releases/${version}/Everything/x86_64/os/repodata/${sha256}-primary.xml.gz"; + sha256 = "880055a50c05b20641530d09b23f64501a000b2f92fe252417c530178730a95e"; + }; + urlPrefix = "mirror://fedora/linux/releases/${version}/Everything/x86_64/os"; + archs = ["noarch" "x86_64"]; + packages = commonFedoraPackages ++ [ "cronie" "util-linux" ]; + unifiedSystemDir = true; + }; + + fedora27x86_64 = + let version = "27"; + in rec { + name = "fedora-${version}-x86_64"; + fullName = "Fedora ${version} (x86_64)"; + packagesList = fetchurl rec { + url = "mirror://fedora/linux/releases/${version}/Everything/x86_64/os/repodata/${sha256}-primary.xml.gz"; + sha256 = "48986ce4583cd09825c6d437150314446f0f49fa1a1bd62dcfa1085295030fe9"; + }; + urlPrefix = "mirror://fedora/linux/releases/${version}/Everything/x86_64/os"; + archs = ["noarch" "x86_64"]; + packages = commonFedoraPackages ++ [ "cronie" "util-linux" ]; + unifiedSystemDir = true; + }; + + centos6i386 = + let version = "6.9"; + in rec { + name = "centos-${version}-i386"; + fullName = "CentOS ${version} (i386)"; + # N.B. Switch to vault.centos.org when the next release comes out + # urlPrefix = "http://vault.centos.org/${version}/os/i386"; + urlPrefix = "http://mirror.centos.org/centos-6/${version}/os/i386"; + packagesList = fetchurl rec { + url = "${urlPrefix}/repodata/${sha256}-primary.xml.gz"; + sha256 = "b826a45082ef68340325c0855f3d2e5d5a4d0f77d28ba3b871791d6f14a97aeb"; + }; + archs = ["noarch" "i386"]; + packages = commonCentOSPackages ++ [ "procps" ]; + }; + + centos6x86_64 = + let version = "6.9"; + in rec { + name = "centos-${version}-x86_64"; + fullName = "CentOS ${version} (x86_64)"; + # N.B. Switch to vault.centos.org when the next release comes out + # urlPrefix = "http://vault.centos.org/${version}/os/x86_64"; + urlPrefix = "http://mirror.centos.org/centos-6/${version}/os/x86_64"; + packagesList = fetchurl rec { + url = "${urlPrefix}/repodata/${sha256}-primary.xml.gz"; + sha256 = "ed2b2d4ac98d774d4cd3e91467e1532f7e8b0275cfc91a0d214b532dcaf1e979"; + }; + archs = ["noarch" "x86_64"]; + packages = commonCentOSPackages ++ [ "procps" ]; + }; + + # Note: no i386 release for 7.x + centos7x86_64 = + let version = "7.4.1708"; + in rec { + name = "centos-${version}-x86_64"; + fullName = "CentOS ${version} (x86_64)"; + # N.B. Switch to vault.centos.org when the next release comes out + # urlPrefix = "http://vault.centos.org/${version}/os/x86_64"; + urlPrefix = "http://mirror.centos.org/centos-7/${version}/os/x86_64"; + packagesList = fetchurl rec { + url = "${urlPrefix}/repodata/${sha256}-primary.xml.gz"; + sha256 = "b686d3a0f337323e656d9387b9a76ce6808b26255fc3a138b1a87d3b1cb95ed5"; + }; + archs = ["noarch" "x86_64"]; + packages = commonCentOSPackages ++ [ "procps-ng" ]; + }; + }; + + + /* The set of supported Dpkg-based distributions. */ + + debDistros = rec { + + # Interestingly, the SHA-256 hashes provided by Ubuntu in + # http://nl.archive.ubuntu.com/ubuntu/dists/{gutsy,hardy}/Release are + # wrong, but the SHA-1 and MD5 hashes are correct. Intrepid is fine. + + ubuntu1204i386 = { + name = "ubuntu-12.04-precise-i386"; + fullName = "Ubuntu 12.04 Precise (i386)"; + packagesLists = + [ (fetchurl { + url = mirror://ubuntu/dists/precise/main/binary-i386/Packages.bz2; + sha256 = "18ns9h4qhvjfcip9z55grzi371racxavgqkp6b5kfkdq2wwwax2d"; + }) + (fetchurl { + url = mirror://ubuntu/dists/precise/universe/binary-i386/Packages.bz2; + sha256 = "085lkzbnzkc74kfdmwdc32sfqyfz8dr0rbiifk8kx9jih3xjw2jk"; + }) + ]; + urlPrefix = mirror://ubuntu; + packages = commonDebPackages ++ [ "diffutils" ]; + }; + + ubuntu1204x86_64 = { + name = "ubuntu-12.04-precise-amd64"; + fullName = "Ubuntu 12.04 Precise (amd64)"; + packagesLists = + [ (fetchurl { + url = mirror://ubuntu/dists/precise/main/binary-amd64/Packages.bz2; + sha256 = "1aabpn0hdih6cbabyn87yvhccqj44q9k03mqmjsb920iqlckl3fc"; + }) + (fetchurl { + url = mirror://ubuntu/dists/precise/universe/binary-amd64/Packages.bz2; + sha256 = "0x4hz5aplximgb7gnpvrhkw8m7a40s80rkm5b8hil0afblwlg4vr"; + }) + ]; + urlPrefix = mirror://ubuntu; + packages = commonDebPackages ++ [ "diffutils" ]; + }; + + ubuntu1404i386 = { + name = "ubuntu-14.04-trusty-i386"; + fullName = "Ubuntu 14.04 Trusty (i386)"; + packagesLists = + [ (fetchurl { + url = mirror://ubuntu/dists/trusty/main/binary-i386/Packages.bz2; + sha256 = "1d5y3v3v079gdq45hc07ja0bjlmzqfwdwwlq0brwxi8m75k3iz7x"; + }) + (fetchurl { + url = mirror://ubuntu/dists/trusty/universe/binary-i386/Packages.bz2; + sha256 = "03x9w92by320rfklrqhcl3qpwmnxds9c8ijl5zhcb21d6dcz5z1a"; + }) + ]; + urlPrefix = mirror://ubuntu; + packages = commonDebPackages ++ [ "diffutils" "libc-bin" ]; + }; + + ubuntu1404x86_64 = { + name = "ubuntu-14.04-trusty-amd64"; + fullName = "Ubuntu 14.04 Trusty (amd64)"; + packagesLists = + [ (fetchurl { + url = mirror://ubuntu/dists/trusty/main/binary-amd64/Packages.bz2; + sha256 = "1hhzbyqfr5i0swahwnl5gfp5l9p9hspywb1vpihr3b74p1z935bh"; + }) + (fetchurl { + url = mirror://ubuntu/dists/trusty/universe/binary-amd64/Packages.bz2; + sha256 = "04560ba8s4z4v5iawknagrkn9q1nzvpn081ycmqvhh73p3p3g1jm"; + }) + ]; + urlPrefix = mirror://ubuntu; + packages = commonDebPackages ++ [ "diffutils" "libc-bin" ]; + }; + + ubuntu1604i386 = { + name = "ubuntu-16.04-xenial-i386"; + fullName = "Ubuntu 16.04 Xenial (i386)"; + packagesLists = + [ (fetchurl { + url = mirror://ubuntu/dists/xenial/main/binary-i386/Packages.xz; + sha256 = "13r75sp4slqy8w32y5dnr7pp7p3cfvavyr1g7gwnlkyrq4zx4ahy"; + }) + (fetchurl { + url = mirror://ubuntu/dists/xenial/universe/binary-i386/Packages.xz; + sha256 = "14fid1rqm3sc0wlygcvn0yx5aljf51c2jpd4x0zxij4019316hsh"; + }) + ]; + urlPrefix = mirror://ubuntu; + packages = commonDebPackages ++ [ "diffutils" "libc-bin" ]; + }; + + ubuntu1604x86_64 = { + name = "ubuntu-16.04-xenial-amd64"; + fullName = "Ubuntu 16.04 Xenial (amd64)"; + packagesLists = + [ (fetchurl { + url = mirror://ubuntu/dists/xenial/main/binary-amd64/Packages.xz; + sha256 = "110qnkhjkkwm316fbig3aivm2595ydz6zskc4ld5cr8ngcrqm1bn"; + }) + (fetchurl { + url = mirror://ubuntu/dists/xenial/universe/binary-amd64/Packages.xz; + sha256 = "0mm7gj491yi6q4v0n4qkbsm94s59bvqir6fk60j73w7y4la8rg68"; + }) + ]; + urlPrefix = mirror://ubuntu; + packages = commonDebPackages ++ [ "diffutils" "libc-bin" ]; + }; + + ubuntu1710i386 = { + name = "ubuntu-17.10-artful-i386"; + fullName = "Ubuntu 17.10 Artful (i386)"; + packagesLists = + [ (fetchurl { + url = mirror://ubuntu/dists/artful/main/binary-i386/Packages.xz; + sha256 = "18yrj4kqdzm39q0527m97h5ing58hkm9yq9iyj636zh2rclym3c8"; + }) + (fetchurl { + url = mirror://ubuntu/dists/artful/universe/binary-i386/Packages.xz; + sha256 = "1v0njw2w80xfmxi7by76cs8hyxlla5h3gqajlpdw5srjgx2qrm2g"; + }) + ]; + urlPrefix = mirror://ubuntu; + packages = commonDebPackages ++ [ "diffutils" "libc-bin" ]; + }; + + ubuntu1710x86_64 = { + name = "ubuntu-17.10-artful-amd64"; + fullName = "Ubuntu 17.10 Artful (amd64)"; + packagesLists = + [ (fetchurl { + url = mirror://ubuntu/dists/artful/main/binary-amd64/Packages.xz; + sha256 = "104g57j1l3vi8wb5f7rgjvjhf82ccs0vwhc59jfc4ynd51z7fqjk"; + }) + (fetchurl { + url = mirror://ubuntu/dists/artful/universe/binary-amd64/Packages.xz; + sha256 = "1qzs95wfy9inaskfx9cf1l5yd3aaqwzy72zzi9xyvkxi75k5gcn4"; + }) + ]; + urlPrefix = mirror://ubuntu; + packages = commonDebPackages ++ [ "diffutils" "libc-bin" ]; + }; + + debian8i386 = { + name = "debian-8.11-jessie-i386"; + fullName = "Debian 8.11 Jessie (i386)"; + packagesList = fetchurl { + url = mirror://debian/dists/jessie/main/binary-i386/Packages.xz; + sha256 = "0adblarhx50yga900il6m25ng0csa81i3wid1dxxmydbdmri7v7d"; + }; + urlPrefix = mirror://debian; + packages = commonDebianPackages; + }; + + debian8x86_64 = { + name = "debian-8.11-jessie-amd64"; + fullName = "Debian 8.11 Jessie (amd64)"; + packagesList = fetchurl { + url = mirror://debian/dists/jessie/main/binary-amd64/Packages.xz; + sha256 = "09y1mv4kqllhxpk1ibjsyl5jig5bp0qxw6pp4sn56rglrpygmn5x"; + }; + urlPrefix = mirror://debian; + packages = commonDebianPackages; + }; + + debian9i386 = { + name = "debian-9.4-stretch-i386"; + fullName = "Debian 9.4 Stretch (i386)"; + packagesList = fetchurl { + url = http://snapshot.debian.org/archive/debian/20180912T154744Z/dists/stretch/main/binary-i386/Packages.xz; + sha256 = "0flvn8zn7vk04p10ndf3aq0mdr8k2ic01g51aq4lsllkv8lmwzyh"; + }; + urlPrefix = mirror://debian; + packages = commonDebianPackages; + }; + + debian9x86_64 = { + name = "debian-9.4-stretch-amd64"; + fullName = "Debian 9.4 Stretch (amd64)"; + packagesList = fetchurl { + url = http://snapshot.debian.org/archive/debian/20180912T154744Z/dists/stretch/main/binary-amd64/Packages.xz; + sha256 = "11vnn9bba2jabixvabfbw9zparl326c88xn99di7pbr5xsnl15jm"; + }; + urlPrefix = mirror://debian; + packages = commonDebianPackages; + }; + + + }; + + + /* Common packages for Fedora images. */ + commonFedoraPackages = [ + "autoconf" + "automake" + "basesystem" + "bzip2" + "curl" + "diffutils" + "fedora-release" + "findutils" + "gawk" + "gcc-c++" + "gzip" + "make" + "patch" + "perl" + "pkgconf-pkg-config" + "rpm" + "rpm-build" + "tar" + "unzip" + ]; + + commonCentOSPackages = [ + "autoconf" + "automake" + "basesystem" + "bzip2" + "curl" + "diffutils" + "centos-release" + "findutils" + "gawk" + "gcc-c++" + "gzip" + "make" + "patch" + "perl" + "pkgconfig" + "rpm" + "rpm-build" + "tar" + "unzip" + ]; + + commonRHELPackages = [ + "autoconf" + "automake" + "basesystem" + "bzip2" + "curl" + "diffutils" + "findutils" + "gawk" + "gcc-c++" + "gzip" + "make" + "patch" + "perl" + "pkgconfig" + "procps-ng" + "rpm" + "rpm-build" + "tar" + "unzip" + ]; + + /* Common packages for openSUSE images. */ + commonOpenSUSEPackages = [ + "aaa_base" + "autoconf" + "automake" + "bzip2" + "curl" + "diffutils" + "findutils" + "gawk" + "gcc-c++" + "gzip" + "make" + "patch" + "perl" + "pkg-config" + "rpm" + "tar" + "unzip" + "util-linux" + "gnu-getopt" + ]; + + + /* Common packages for Debian/Ubuntu images. */ + commonDebPackages = [ + "base-passwd" + "dpkg" + "libc6-dev" + "perl" + "bash" + "dash" + "gzip" + "bzip2" + "tar" + "grep" + "mawk" + "sed" + "findutils" + "g++" + "make" + "curl" + "patch" + "locales" + "coreutils" + # Needed by checkinstall: + "util-linux" + "file" + "dpkg-dev" + "pkg-config" + # Needed because it provides /etc/login.defs, whose absence causes + # the "passwd" post-installs script to fail. + "login" + "passwd" + ]; + + commonDebianPackages = commonDebPackages ++ [ "sysvinit" "diff" "mktemp" ]; + + + /* A set of functions that build the Linux distributions specified + in `rpmDistros' and `debDistros'. For instance, + `diskImageFuns.ubuntu1004x86_64 { }' builds an Ubuntu 10.04 disk + image containing the default packages specified above. Overrides + of the default image parameters can be given. In particular, + `extraPackages' specifies the names of additional packages from + the distribution that should be included in the image; `packages' + allows the entire set of packages to be overriden; and `size' + sets the size of the disk in megabytes. E.g., + `diskImageFuns.ubuntu1004x86_64 { extraPackages = ["firefox"]; + size = 8192; }' builds an 8 GiB image containing Firefox in + addition to the default packages. */ + diskImageFuns = + (lib.mapAttrs (name: as: as2: makeImageFromRPMDist (as // as2)) rpmDistros) // + (lib.mapAttrs (name: as: as2: makeImageFromDebDist (as // as2)) debDistros); + + + /* Shorthand for `diskImageFuns.<attr> { extraPackages = ... }'. */ + diskImageExtraFuns = + lib.mapAttrs (name: f: extraPackages: f { inherit extraPackages; }) diskImageFuns; + + + /* Default disk images generated from the `rpmDistros' and + `debDistros' sets. */ + diskImages = lib.mapAttrs (name: f: f {}) diskImageFuns; + +} // import ./windows pkgs diff --git a/nixpkgs/pkgs/build-support/vm/rpm/rpm-closure.pl b/nixpkgs/pkgs/build-support/vm/rpm/rpm-closure.pl new file mode 100644 index 000000000000..6442cd91a957 --- /dev/null +++ b/nixpkgs/pkgs/build-support/vm/rpm/rpm-closure.pl @@ -0,0 +1,184 @@ +use strict; +use XML::Simple; +use List::Util qw(min); + +my @packagesFiles = (); +my @urlPrefixes = (); + +# rpm-closure.pl (<package-file> <url-prefix>)+ <toplevel-pkg>+ + +while(-f $ARGV[0]) { + my $packagesFile = shift @ARGV; + my $urlPrefix = shift @ARGV; + push(@packagesFiles, $packagesFile); + push(@urlPrefixes, $urlPrefix); +} + + +sub rpmvercmp { + my ($version1, $version2) = @_; + my @vercmps1 = split /\./, $version1; + my @vercmps2 = split /\./, $version2; + my $l1 = scalar(@vercmps1); + my $l2 = scalar(@vercmps2); + my $l = min($l1, $l2); + + for(my $i=0; $i<$l; $i++) { + my $v1 = $vercmps1[$i]; + my $v2 = $vercmps2[$i]; + + if($v1 =~ /^[0-9]*$/ && $v2 =~ /^[0-9]*$/) { + if ( int($v1) > int($v2) ) { + return 1; + } + elsif ( int($v1) < int($v2) ) { + return -1; + } + } else { + if ( $v1 gt $v2 ) { + return 1; + } + elsif ( $v1 lt $v2 ) { + return -1; + } + } + } + if($l1 == $l2) { + return 0; + } elsif ($l1 > $l2) { + return 1; + } elsif ($l1 < $l2) { + return -1; + } +} + +my @toplevelPkgs = @ARGV; + +my @archs = split ' ', ($ENV{'archs'} or ""); + +my %pkgs; +for (my $i = 0; $i < scalar(@packagesFiles); $i++) { + my $packagesFile = $packagesFiles[$i]; + print STDERR "parsing packages in $packagesFile...\n"; + + my $xml = XMLin($packagesFile, ForceArray => ['package', 'rpm:entry', 'file'], KeyAttr => []) or die; + + print STDERR "$packagesFile contains $xml->{packages} packages\n"; + + foreach my $pkg (@{$xml->{'package'}}) { + if (scalar @archs > 0) { + my $arch = $pkg->{arch}; + my $found = 0; + foreach my $a (@archs) { $found = 1 if $arch eq $a; } + next if !$found; + } + if (defined $pkgs{$pkg->{name}}) { + my $earlierPkg = $pkgs{$pkg->{name}}; + print STDERR "WARNING: duplicate occurrence of package $pkg->{name}\n"; + # <version epoch="0" ver="1.28.0" rel="2.el6"/> + my $cmp = rpmvercmp($pkg->{'version'}->{ver}, $earlierPkg->{'version'}->{ver}); + if ($cmp > 0 || ($cmp == 0 && rpmvercmp($pkg->{'version'}->{rel}, $earlierPkg->{'version'}->{rel})>0)) { + print STDERR "WARNING: replaced package $pkg->{name} (".$earlierPkg->{'version'}->{ver}." ".$earlierPkg->{'version'}->{rel}.") with newer one (".$pkg->{'version'}->{ver}." ".$pkg->{'version'}->{rel}.")\n"; + $pkg->{urlPrefix} = $urlPrefixes[$i]; + $pkgs{$pkg->{name}} = $pkg; + } + next; + } + $pkg->{urlPrefix} = $urlPrefixes[$i]; + $pkgs{$pkg->{name}} = $pkg; + } +} + +my %provides; +PKG: foreach my $pkgName (sort(keys %pkgs)) { + #print STDERR "looking at $pkgName\n"; + my $pkg = $pkgs{$pkgName}; + + # Skip packages that conflict with a required package. + my $conflicts = $pkg->{format}->{'rpm:conflicts'}->{'rpm:entry'} // []; + foreach my $conflict (@{$conflicts}) { + next if $conflict->{flags} // "" eq "LT" || $conflict->{flags} // "" eq "LE"; + #print STDERR " $pkgName conflicts with $conflict->{name}\n"; + if (grep { $_ eq $conflict->{name} } @toplevelPkgs) { + print STDERR "skipping package $pkgName because it conflicts with a required package\n"; + next PKG; + } + } + + my $provides = $pkg->{format}->{'rpm:provides'}->{'rpm:entry'} or die; + foreach my $req (@{$provides}) { + #print STDERR " $pkgName provides $req->{name}\n"; + #die "multiple provides for $req->{name}" if defined $provides{$req->{name}}; + $provides{$req->{name}} = $pkgName; + } + + if (defined $pkg->{format}->{file}) { + foreach my $file (@{$pkg->{format}->{file}}) { + #print STDERR " provides file $file\n"; + $provides{$file} = $pkgName; + } + } +} + + +my %donePkgs; +my @needed = (); + +sub closePackage { + my $pkgName = shift; + + return if defined $donePkgs{$pkgName}; + $donePkgs{$pkgName} = 1; + + print STDERR ">>> $pkgName\n"; + + my $pkg = $pkgs{$pkgName} or die "package $pkgName doesn't exist"; + + my $requires = $pkg->{format}->{'rpm:requires'}->{'rpm:entry'} || []; + + my @deps = (); + foreach my $req (@{$requires}) { + next if $req->{name} =~ /^rpmlib\(/; + #print STDERR " needs $req->{name}\n"; + my $provider = $provides{$req->{name}}; + if (!defined $provider) { + print STDERR " WARNING: no provider for $req->{name}\n"; + next; + } + #print STDERR " satisfied by $provider\n"; + push @deps, $provider; + } + + closePackage($_) foreach @deps; + + push @needed, $pkgName; +} + + +foreach my $pkgName (@toplevelPkgs) { + closePackage $pkgName; +} + + +# Generate the output Nix expression. +print "# This is a generated file. Do not modify!\n"; +print "# Following are the RPM packages constituting the closure of: @toplevelPkgs\n\n"; +print "{fetchurl}:\n\n"; +print "[\n\n"; + +foreach my $pkgName (@needed) { + my $pkg = $pkgs{$pkgName}; + print " (fetchurl {\n"; + print " url = $pkg->{urlPrefix}/$pkg->{location}->{href};\n"; + if ($pkg->{checksum}->{type} eq "sha") { + print " sha1 = \"$pkg->{checksum}->{content}\";\n"; + } elsif ($pkg->{checksum}->{type} eq "sha256") { + print " sha256 = \"$pkg->{checksum}->{content}\";\n"; + } else { + die "unsupported hash type"; + } + print " })\n"; + print "\n"; +} + +print "]\n"; diff --git a/nixpkgs/pkgs/build-support/vm/test.nix b/nixpkgs/pkgs/build-support/vm/test.nix new file mode 100644 index 000000000000..d0d85fce3662 --- /dev/null +++ b/nixpkgs/pkgs/build-support/vm/test.nix @@ -0,0 +1,39 @@ +with import ../../.. {}; +with vmTools; + +rec { + + + # Run the PatchELF derivation in a VM. + buildPatchelfInVM = runInLinuxVM patchelf; + + buildHelloInVM = runInLinuxVM hello; + + buildPanInVM = runInLinuxVM pan; + + + testRPMImage = makeImageTestScript diskImages.fedora16x86_64; + + + buildPatchelfRPM = buildRPM { + name = "patchelf-rpm"; + src = patchelf.src; + diskImage = diskImages.fedora16x86_64; + }; + + + testUbuntuImage = makeImageTestScript diskImages.ubuntu810i386; + + + buildInDebian = runInLinuxImage (stdenv.mkDerivation { + name = "deb-compile"; + src = patchelf.src; + diskImage = diskImages.ubuntu1204i386; + memSize = 512; + phases = "sysInfoPhase unpackPhase patchPhase configurePhase buildPhase checkPhase installPhase fixupPhase distPhase"; + sysInfoPhase = '' + dpkg-query --list + ''; + }); + +} diff --git a/nixpkgs/pkgs/build-support/vm/windows/bootstrap.nix b/nixpkgs/pkgs/build-support/vm/windows/bootstrap.nix new file mode 100644 index 000000000000..3b06d8f47490 --- /dev/null +++ b/nixpkgs/pkgs/build-support/vm/windows/bootstrap.nix @@ -0,0 +1,83 @@ +{ stdenv, fetchurl, vmTools, writeScript, writeText, runCommand, makeInitrd +, python, perl, coreutils, dosfstools, gzip, mtools, netcat-gnu, openssh, qemu +, samba, socat, vde2, cdrkit, pathsFromGraph, gnugrep +}: + +{ isoFile, productKey, arch ? null }: + +with stdenv.lib; + +let + controller = import ./controller { + inherit stdenv writeScript vmTools makeInitrd; + inherit samba vde2 openssh socat netcat-gnu coreutils gzip gnugrep; + }; + + mkCygwinImage = import ./cygwin-iso { + inherit stdenv fetchurl runCommand python perl cdrkit pathsFromGraph; + arch = let + defaultArch = if stdenv.is64bit then "x86_64" else "i686"; + in if arch == null then defaultArch else arch; + }; + + installer = import ./install { + inherit controller mkCygwinImage; + inherit stdenv runCommand openssh qemu writeText dosfstools mtools; + }; +in rec { + installedVM = installer { + inherit isoFile productKey; + }; + + runInVM = img: attrs: controller (attrs // { + inherit (installedVM) sshKey; + qemuArgs = attrs.qemuArgs or [] ++ [ + "-boot order=c" + "-drive file=${img},index=0,media=disk" + ]; + }); + + runAndSuspend = let + drives = { + s = { + source = "nixstore"; + target = "/nix/store"; + }; + x = { + source = "xchg"; + target = "/tmp/xchg"; + }; + }; + + genDriveCmds = letter: { source, target }: [ + "net use ${letter}: '\\\\192.168.0.2\\${source}' /persistent:yes" + "mkdir -p '${target}'" + "mount -o bind '/cygdrive/${letter}' '${target}'" + "echo '/cygdrive/${letter} ${target} none bind 0 0' >> /etc/fstab" + ]; + in runInVM "winvm.img" { + command = concatStringsSep " && " ([ + "net config server /autodisconnect:-1" + ] ++ concatLists (mapAttrsToList genDriveCmds drives)); + suspendTo = "state.gz"; + }; + + suspendedVM = stdenv.mkDerivation { + name = "cygwin-suspended-vm"; + buildCommand = '' + ${qemu}/bin/qemu-img create \ + -b "${installedVM}/disk.img" \ + -f qcow2 winvm.img + ${runAndSuspend} + mkdir -p "$out" + cp winvm.img "$out/disk.img" + cp state.gz "$out/state.gz" + ''; + }; + + resumeAndRun = command: runInVM "${suspendedVM}/disk.img" { + resumeFrom = "${suspendedVM}/state.gz"; + qemuArgs = singleton "-snapshot"; + inherit command; + }; +} diff --git a/nixpkgs/pkgs/build-support/vm/windows/controller/default.nix b/nixpkgs/pkgs/build-support/vm/windows/controller/default.nix new file mode 100644 index 000000000000..e000308bed8f --- /dev/null +++ b/nixpkgs/pkgs/build-support/vm/windows/controller/default.nix @@ -0,0 +1,262 @@ +{ stdenv, writeScript, vmTools, makeInitrd +, samba, vde2, openssh, socat, netcat-gnu, coreutils, gnugrep, gzip +}: + +{ sshKey +, qemuArgs ? [] +, command ? "sync" +, suspendTo ? null +, resumeFrom ? null +, installMode ? false +}: + +with stdenv.lib; + +let + preInitScript = writeScript "preinit.sh" '' + #!${vmTools.initrdUtils}/bin/ash -e + export PATH=${vmTools.initrdUtils}/bin + mount -t proc none /proc + mount -t sysfs none /sys + for arg in $(cat /proc/cmdline); do + if [ "x''${arg#command=}" != "x$arg" ]; then + command="''${arg#command=}" + fi + done + + for i in $(cat ${modulesClosure}/insmod-list); do + insmod $i + done + + mkdir -p /dev /fs + + mount -t tmpfs none /dev + mknod /dev/null c 1 3 + mknod /dev/zero c 1 5 + mknod /dev/random c 1 8 + mknod /dev/urandom c 1 9 + mknod /dev/tty c 5 0 + + ifconfig lo up + ifconfig eth0 up 192.168.0.2 + + mount -t tmpfs none /fs + mkdir -p /fs/nix/store /fs/xchg /fs/dev /fs/sys /fs/proc /fs/etc /fs/tmp + + mount -o bind /dev /fs/dev + mount -t sysfs none /fs/sys + mount -t proc none /fs/proc + + mount -t 9p \ + -o trans=virtio,version=9p2000.L,cache=loose \ + store /fs/nix/store + + mount -t 9p \ + -o trans=virtio,version=9p2000.L,cache=loose \ + xchg /fs/xchg + + echo root:x:0:0::/root:/bin/false > /fs/etc/passwd + + set +e + chroot /fs $command $out + echo $? > /fs/xchg/in-vm-exit + + poweroff -f + ''; + + initrd = makeInitrd { + contents = singleton { + object = preInitScript; + symlink = "/init"; + }; + }; + + loopForever = "while :; do ${coreutils}/bin/sleep 1; done"; + + initScript = writeScript "init.sh" ('' + #!${stdenv.shell} + ${coreutils}/bin/cp -L "${sshKey}" /ssh.key + ${coreutils}/bin/chmod 600 /ssh.key + '' + (if installMode then '' + echo -n "Waiting for Windows installation to finish..." + while ! ${netcat-gnu}/bin/netcat -z 192.168.0.1 22; do + echo -n . + # Print a dot every 10 seconds only to shorten line length. + ${coreutils}/bin/sleep 10 + done + ${coreutils}/bin/touch /xchg/waiting_done + echo " success." + # Loop forever, because this VM is going to be killed. + ${loopForever} + '' else '' + ${coreutils}/bin/mkdir -p /etc/samba /etc/samba/private \ + /var/lib/samba /var/log /var/run + ${coreutils}/bin/cat > /etc/samba/smb.conf <<CONFIG + [global] + security = user + map to guest = Bad User + guest account = root + workgroup = cygwin + netbios name = controller + server string = %h + log level = 1 + max log size = 1000 + log file = /var/log/samba.log + + [nixstore] + path = /nix/store + writable = yes + guest ok = yes + + [xchg] + path = /xchg + writable = yes + guest ok = yes + CONFIG + + ${samba}/sbin/nmbd -D + ${samba}/sbin/smbd -D + + echo -n "Waiting for Windows VM to become available..." + while ! ${netcat-gnu}/bin/netcat -z 192.168.0.1 22; do + echo -n . + ${coreutils}/bin/sleep 1 + done + ${coreutils}/bin/touch /xchg/waiting_done + echo " success." + + ${openssh}/bin/ssh \ + -o UserKnownHostsFile=/dev/null \ + -o StrictHostKeyChecking=no \ + -i /ssh.key \ + -l Administrator \ + 192.168.0.1 -- ${lib.escapeShellArg command} + '') + optionalString (suspendTo != null) '' + ${coreutils}/bin/touch /xchg/suspend_now + ${loopForever} + ''); + + kernelAppend = concatStringsSep " " [ + "panic=1" + "loglevel=4" + "console=tty1" + "console=ttyS0" + "command=${initScript}" + ]; + + controllerQemuArgs = concatStringsSep " " (maybeKvm64 ++ [ + "-pidfile $CTRLVM_PIDFILE" + "-nographic" + "-no-reboot" + "-virtfs local,path=/nix/store,security_model=none,mount_tag=store" + "-virtfs local,path=$XCHG_DIR,security_model=none,mount_tag=xchg" + "-kernel ${modulesClosure.kernel}/bzImage" + "-initrd ${initrd}/initrd" + "-append \"${kernelAppend}\"" + "-net nic,vlan=0,macaddr=52:54:00:12:01:02,model=virtio" + "-net vde,vlan=0,sock=$QEMU_VDE_SOCKET" + ]); + + maybeKvm64 = optional (stdenv.hostPlatform.system == "x86_64-linux") "-cpu kvm64"; + + cygwinQemuArgs = concatStringsSep " " (maybeKvm64 ++ [ + "-monitor unix:$MONITOR_SOCKET,server,nowait" + "-pidfile $WINVM_PIDFILE" + "-nographic" + "-net nic,vlan=0,macaddr=52:54:00:12:01:01" + "-net vde,vlan=0,sock=$QEMU_VDE_SOCKET" + "-rtc base=2010-01-01,clock=vm" + ] ++ qemuArgs ++ optionals (resumeFrom != null) [ + "-incoming 'exec: ${gzip}/bin/gzip -c -d \"${resumeFrom}\"'" + ]); + + modulesClosure = overrideDerivation vmTools.modulesClosure (o: { + rootModules = o.rootModules ++ singleton "virtio_net"; + }); + + preVM = '' + (set; declare -p) > saved-env + XCHG_DIR="$(${coreutils}/bin/mktemp -d nix-vm.XXXXXXXXXX --tmpdir)" + ${coreutils}/bin/mv saved-env "$XCHG_DIR/" + + eval "$preVM" + + QEMU_VDE_SOCKET="$(pwd)/vde.ctl" + MONITOR_SOCKET="$(pwd)/monitor" + WINVM_PIDFILE="$(pwd)/winvm.pid" + CTRLVM_PIDFILE="$(pwd)/ctrlvm.pid" + ${vde2}/bin/vde_switch -s "$QEMU_VDE_SOCKET" --dirmode 0700 & + echo 'alive?' | ${socat}/bin/socat - \ + UNIX-CONNECT:$QEMU_VDE_SOCKET/ctl,retry=20 + ''; + + vmExec = '' + ${vmTools.qemuProg} ${controllerQemuArgs} & + ${vmTools.qemuProg} ${cygwinQemuArgs} & + echo -n "Waiting for VMs to start up..." + timeout=60 + while ! test -e "$WINVM_PIDFILE" -a -e "$CTRLVM_PIDFILE"; do + timeout=$(($timeout - 1)) + echo -n . + if test $timeout -le 0; then + echo " timed out." + exit 1 + fi + ${coreutils}/bin/sleep 1 + done + echo " done." + ''; + + checkDropOut = '' + if ! test -e "$XCHG_DIR/waiting_done" && + ! kill -0 $(< "$WINVM_PIDFILE"); then + echo "Windows VM has dropped out early, bailing out!" >&2 + exit 1 + fi + ''; + + toMonitor = "${socat}/bin/socat - UNIX-CONNECT:$MONITOR_SOCKET"; + + postVM = if suspendTo != null then '' + while ! test -e "$XCHG_DIR/suspend_now"; do + ${checkDropOut} + ${coreutils}/bin/sleep 1 + done + ${toMonitor} <<CMD + stop + migrate_set_speed 4095m + migrate "exec:${gzip}/bin/gzip -c > '${suspendTo}'" + CMD + echo -n "Waiting for memory dump to finish..." + while ! echo info migrate | ${toMonitor} | \ + ${gnugrep}/bin/grep -qi '^migration *status: *complete'; do + ${coreutils}/bin/sleep 1 + echo -n . + done + echo " done." + echo quit | ${toMonitor} + wait $(< "$WINVM_PIDFILE") + eval "$postVM" + exit 0 + '' else if installMode then '' + wait $(< "$WINVM_PIDFILE") + eval "$postVM" + exit 0 + '' else '' + while kill -0 $(< "$CTRLVM_PIDFILE"); do + ${checkDropOut} + done + if ! test -e "$XCHG_DIR/in-vm-exit"; then + echo "Virtual machine didn't produce an exit code." + exit 1 + fi + eval "$postVM" + exit $(< "$XCHG_DIR/in-vm-exit") + ''; + +in writeScript "run-cygwin-vm.sh" '' + #!${stdenv.shell} -e + ${preVM} + ${vmExec} + ${postVM} +'' diff --git a/nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/default.nix b/nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/default.nix new file mode 100644 index 000000000000..76cd41a75bc7 --- /dev/null +++ b/nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/default.nix @@ -0,0 +1,56 @@ +{ stdenv, fetchurl, runCommand, python, perl, xorriso, pathsFromGraph +, arch ? "x86_64" +}: + +{ packages ? [] +, mirror ? "http://ftp.gwdg.de/pub/linux/sources.redhat.com/cygwin" +, extraContents ? [] +}: + +let + cygPkgList = if arch == "x86_64" then fetchurl { + url = "${mirror}/x86_64/setup.ini"; + sha256 = "0arrxvxbl85l82iy648snx5cl952w791p45p0dfg1xpiaf96cbkj"; + } else fetchurl { + url = "${mirror}/x86/setup.ini"; + sha256 = "1fayx34868vd5h2nah7chiw65sl3i9qzrwvs7lrlv2h8k412vb69"; + }; + + cygwinCross = (import ../../../../.. { + localSystem = stdenv.hostPlatform; + crossSystem = { + libc = "msvcrt"; + platform = {}; + inherit arch; + config = "${arch}-w64-mingw32"; + }; + }).windows.cygwinSetup; + + makeCygwinClosure = { packages, packageList }: let + expr = import (runCommand "cygwin.nix" { buildInputs = [ python ]; } '' + python ${./mkclosure.py} "${packages}" ${toString packageList} > "$out" + ''); + gen = { url, hash }: { + source = fetchurl { + url = "${mirror}/${url}"; + sha512 = hash; + }; + target = url; + }; + in map gen expr; + +in import ../../../../../nixos/lib/make-iso9660-image.nix { + inherit stdenv perl xorriso pathsFromGraph; + syslinux = null; + contents = [ + { source = "${cygwinCross}/bin/setup.exe"; + target = "setup.exe"; + } + { source = cygPkgList; + target = "setup.ini"; + } + ] ++ makeCygwinClosure { + packages = cygPkgList; + packageList = packages; + } ++ extraContents; +} diff --git a/nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/mkclosure.py b/nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/mkclosure.py new file mode 100644 index 000000000000..4c0d67c43bac --- /dev/null +++ b/nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/mkclosure.py @@ -0,0 +1,78 @@ +# Ugliest Python code I've ever written. -- aszlig +import sys + +def get_plist(path): + in_pack = False + in_str = False + current_key = None + buf = "" + packages = {} + package_name = None + package_attrs = {} + with open(path, 'r') as setup: + for line in setup: + if in_str and line.rstrip().endswith('"'): + package_attrs[current_key] = buf + line.rstrip()[:-1] + in_str = False + continue + elif in_str: + buf += line + continue + + if line.startswith('@'): + in_pack = True + package_name = line[1:].strip() + package_attrs = {} + elif in_pack and ':' in line: + key, value = line.split(':', 1) + if value.lstrip().startswith('"'): + if value.lstrip()[1:].rstrip().endswith('"'): + value = value.strip().strip('"') + else: + in_str = True + current_key = key.strip().lower() + buf = value.lstrip()[1:] + continue + package_attrs[key.strip().lower()] = value.strip() + elif in_pack: + in_pack = False + packages[package_name] = package_attrs + return packages + +def main(): + packages = get_plist(sys.argv[1]) + to_include = set() + + def traverse(package): + to_include.add(package) + attrs = packages.get(package, {}) + deps = attrs.get('requires', '').split() + for new_dep in set(deps) - to_include: + traverse(new_dep) + + map(traverse, sys.argv[2:]) + + sys.stdout.write('[\n') + for package, attrs in packages.iteritems(): + if package not in to_include: + cats = [c.lower() for c in attrs.get('category', '').split()] + if 'base' not in cats: + continue + + install_line = attrs.get('install') + if install_line is None: + continue + + url, size, hash = install_line.split(' ', 2) + + pack = [ + ' {', + ' url = "{0}";'.format(url), + ' hash = "{0}";'.format(hash), + ' }', + ]; + sys.stdout.write('\n'.join(pack) + '\n') + sys.stdout.write(']\n') + +if __name__ == '__main__': + main() diff --git a/nixpkgs/pkgs/build-support/vm/windows/default.nix b/nixpkgs/pkgs/build-support/vm/windows/default.nix new file mode 100644 index 000000000000..e5ff13f0da9d --- /dev/null +++ b/nixpkgs/pkgs/build-support/vm/windows/default.nix @@ -0,0 +1,45 @@ +#note: the hardcoded /bin/sh is required for the VM's cygwin shell +pkgs: + +let + bootstrapper = import ./bootstrap.nix { + inherit (pkgs) stdenv vmTools writeScript writeText runCommand makeInitrd; + inherit (pkgs) coreutils dosfstools gzip mtools netcat-gnu openssh qemu samba; + inherit (pkgs) socat vde2 fetchurl python perl cdrkit pathsFromGraph; + inherit (pkgs) gnugrep; + }; + + builder = '' + source /tmp/xchg/saved-env 2> /dev/null || true + export NIX_STORE=/nix/store + export NIX_BUILD_TOP=/tmp + export TMPDIR=/tmp + export PATH=/empty + cd "$NIX_BUILD_TOP" + exec $origBuilder $origArgs + ''; + +in { + runInWindowsVM = drv: let + in pkgs.lib.overrideDerivation drv (attrs: let + bootstrap = bootstrapper attrs.windowsImage; + in { + requiredSystemFeatures = [ "kvm" ]; + builder = "${pkgs.stdenv.shell}"; + args = ["-e" (bootstrap.resumeAndRun builder)]; + windowsImage = bootstrap.suspendedVM; + origArgs = attrs.args; + origBuilder = if attrs.builder == attrs.stdenv.shell + then "/bin/sh" + else attrs.builder; + + postHook = '' + PATH=/usr/bin:/bin:/usr/sbin:/sbin + SHELL=/bin/sh + eval "$origPostHook" + ''; + + origPostHook = attrs.postHook or ""; + fixupPhase = ":"; + }); +} diff --git a/nixpkgs/pkgs/build-support/vm/windows/install/default.nix b/nixpkgs/pkgs/build-support/vm/windows/install/default.nix new file mode 100644 index 000000000000..fe8e8f61de02 --- /dev/null +++ b/nixpkgs/pkgs/build-support/vm/windows/install/default.nix @@ -0,0 +1,74 @@ +{ stdenv, runCommand, openssh, qemu, controller, mkCygwinImage +, writeText, dosfstools, mtools +}: + +{ isoFile +, productKey +}: + +let + bootstrapAfterLogin = runCommand "bootstrap.sh" {} '' + cat > "$out" <<EOF + mkdir -p ~/.ssh + cat > ~/.ssh/authorized_keys <<PUBKEY + $(cat "${cygwinSshKey}/key.pub") + PUBKEY + ssh-host-config -y -c 'binmode ntsec' -w dummy + cygrunsrv -S sshd + shutdown -s 5 + EOF + ''; + + cygwinSshKey = stdenv.mkDerivation { + name = "snakeoil-ssh-cygwin"; + buildCommand = '' + mkdir -p "$out" + ${openssh}/bin/ssh-keygen -t ecdsa -f "$out/key" -N "" + ''; + }; + + sshKey = "${cygwinSshKey}/key"; + + packages = [ "openssh" "shutdown" ]; + + floppyCreator = import ./unattended-image.nix { + inherit stdenv writeText dosfstools mtools; + }; + + instfloppy = floppyCreator { + cygwinPackages = packages; + inherit productKey; + }; + + cygiso = mkCygwinImage { + inherit packages; + extraContents = stdenv.lib.singleton { + source = bootstrapAfterLogin; + target = "bootstrap.sh"; + }; + }; + + installController = controller { + inherit sshKey; + installMode = true; + qemuArgs = [ + "-boot order=c,once=d" + "-drive file=${instfloppy},readonly,index=0,if=floppy" + "-drive file=winvm.img,index=0,media=disk" + "-drive file=${isoFile},index=1,media=cdrom" + "-drive file=${cygiso}/iso/cd.iso,index=2,media=cdrom" + ]; + }; + +in stdenv.mkDerivation { + name = "cygwin-base-vm"; + buildCommand = '' + ${qemu}/bin/qemu-img create -f qcow2 winvm.img 2G + ${installController} + mkdir -p "$out" + cp winvm.img "$out/disk.img" + ''; + passthru = { + inherit sshKey; + }; +} diff --git a/nixpkgs/pkgs/build-support/vm/windows/install/unattended-image.nix b/nixpkgs/pkgs/build-support/vm/windows/install/unattended-image.nix new file mode 100644 index 000000000000..5b1ff84cf44d --- /dev/null +++ b/nixpkgs/pkgs/build-support/vm/windows/install/unattended-image.nix @@ -0,0 +1,123 @@ +{ stdenv, writeText, dosfstools, mtools }: + +{ productKey +, shExecAfterwards ? "E:\\bootstrap.sh" +, cygwinRoot ? "C:\\cygwin" +, cygwinSetup ? "E:\\setup.exe" +, cygwinRepository ? "E:\\" +, cygwinPackages ? [ "openssh" ] +}: + +let + afterSetup = [ + cygwinSetup + "-L -n -q" + "-l ${cygwinRepository}" + "-R ${cygwinRoot}" + "-C base" + ] ++ map (p: "-P ${p}") cygwinPackages; + + winXpUnattended = writeText "winnt.sif" '' + [Data] + AutoPartition = 1 + AutomaticUpdates = 0 + MsDosInitiated = 0 + UnattendedInstall = Yes + + [Unattended] + DUDisable = Yes + DriverSigningPolicy = Ignore + Hibernation = No + OemPreinstall = No + OemSkipEula = Yes + Repartition = Yes + TargetPath = \WINDOWS + UnattendMode = FullUnattended + UnattendSwitch = Yes + WaitForReboot = No + + [GuiUnattended] + AdminPassword = "nopasswd" + AutoLogon = Yes + AutoLogonCount = 1 + OEMSkipRegional = 1 + OemSkipWelcome = 1 + ServerWelcome = No + TimeZone = 85 + + [UserData] + ComputerName = "cygwin" + FullName = "cygwin" + OrgName = "" + ProductKey = "${productKey}" + + [Networking] + InstallDefaultComponents = Yes + + [Identification] + JoinWorkgroup = cygwin + + [NetAdapters] + PrimaryAdapter = params.PrimaryAdapter + + [params.PrimaryAdapter] + InfID = * + + [params.MS_MSClient] + + [NetProtocols] + MS_TCPIP = params.MS_TCPIP + + [params.MS_TCPIP] + AdapterSections=params.MS_TCPIP.PrimaryAdapter + + [params.MS_TCPIP.PrimaryAdapter] + DHCP = No + IPAddress = 192.168.0.1 + SpecificTo = PrimaryAdapter + SubnetMask = 255.255.255.0 + WINS = No + + ; Turn off all components + [Components] + ${stdenv.lib.concatMapStrings (comp: "${comp} = Off\n") [ + "AccessOpt" "Appsrv_console" "Aspnet" "BitsServerExtensionsISAPI" + "BitsServerExtensionsManager" "Calc" "Certsrv" "Certsrv_client" + "Certsrv_server" "Charmap" "Chat" "Clipbook" "Cluster" "Complusnetwork" + "Deskpaper" "Dialer" "Dtcnetwork" "Fax" "Fp_extensions" "Fp_vdir_deploy" + "Freecell" "Hearts" "Hypertrm" "IEAccess" "IEHardenAdmin" "IEHardenUser" + "Iis_asp" "Iis_common" "Iis_ftp" "Iis_inetmgr" "Iis_internetdataconnector" + "Iis_nntp" "Iis_serversideincludes" "Iis_smtp" "Iis_webdav" "Iis_www" + "Indexsrv_system" "Inetprint" "Licenseserver" "Media_clips" "Media_utopia" + "Minesweeper" "Mousepoint" "Msmq_ADIntegrated" "Msmq_Core" + "Msmq_HTTPSupport" "Msmq_LocalStorage" "Msmq_MQDSService" + "Msmq_RoutingSupport" "Msmq_TriggersService" "Msnexplr" "Mswordpad" + "Netcis" "Netoc" "OEAccess" "Objectpkg" "Paint" "Pinball" "Pop3Admin" + "Pop3Service" "Pop3Srv" "Rec" "Reminst" "Rootautoupdate" "Rstorage" "SCW" + "Sakit_web" "Solitaire" "Spider" "TSWebClient" "Templates" + "TerminalServer" "UDDIAdmin" "UDDIDatabase" "UDDIWeb" "Vol" "WMAccess" + "WMPOCM" "WbemMSI" "Wms" "Wms_admin_asp" "Wms_admin_mmc" "Wms_isapi" + "Wms_server" "Zonegames" + ]} + + [WindowsFirewall] + Profiles = WindowsFirewall.TurnOffFirewall + + [WindowsFirewall.TurnOffFirewall] + Mode = 0 + + [SetupParams] + UserExecute = "${stdenv.lib.concatStringsSep " " afterSetup}" + + [GuiRunOnce] + Command0 = "${cygwinRoot}\bin\bash -l ${shExecAfterwards}" + ''; + +in stdenv.mkDerivation { + name = "unattended-floppy.img"; + buildCommand = '' + dd if=/dev/zero of="$out" count=1440 bs=1024 + ${dosfstools}/sbin/mkfs.msdos "$out" + ${mtools}/bin/mcopy -i "$out" "${winXpUnattended}" ::winnt.sif + ''; +} |