summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--nixos/lib/make-ext4-fs.nix88
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix40
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix46
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image.nix127
4 files changed, 301 insertions, 0 deletions
diff --git a/nixos/lib/make-ext4-fs.nix b/nixos/lib/make-ext4-fs.nix
new file mode 100644
index 00000000000..23839ea487d
--- /dev/null
+++ b/nixos/lib/make-ext4-fs.nix
@@ -0,0 +1,88 @@
+# Builds an ext4 image containing a populated /nix/store with the closure
+# of store paths passed in the storePaths parameter. The generated image
+# is sized to only fit its contents, with the expectation that a script
+# resizes the filesystem at boot time.
+{ pkgs
+, storePaths
+, volumeLabel
+}:
+
+pkgs.stdenv.mkDerivation {
+  name = "ext4-fs.img";
+
+  buildInputs = with pkgs; [e2fsprogs libfaketime perl];
+
+  # For obtaining the closure of `storePaths'.
+  exportReferencesGraph =
+    map (x: [("closure-" + baseNameOf x) x]) storePaths;
+
+  buildCommand =
+    ''
+      # Add the closures of the top-level store objects.
+      storePaths=$(perl ${pkgs.pathsFromGraph} closure-*)
+
+      # Also include a manifest of the closures in a format suitable
+      # for nix-store --load-db.
+      printRegistration=1 perl ${pkgs.pathsFromGraph} closure-* > nix-path-registration
+
+      # Make a crude approximation of the size of the target image.
+      # If the script starts failing, increase the fudge factors here.
+      numInodes=$(find $storePaths | wc -l)
+      numDataBlocks=$(du -c -B 4096 --apparent-size $storePaths | awk '$2 == "total" { print int($1 * 1.03) }')
+      bytes=$((2 * 4096 * $numInodes + 4096 * $numDataBlocks))
+      echo "Creating an EXT4 image of $bytes bytes (numInodes=$numInodes, numDataBlocks=$numDataBlocks)"
+
+      truncate -s $bytes $out
+      faketime "1970-01-01 00:00:00" mkfs.ext4 -L ${volumeLabel} -U 44444444-4444-4444-8888-888888888888 $out
+
+      # Populate the image contents by piping a bunch of commands to the `debugfs` tool from e2fsprogs.
+      # For example, to copy /nix/store/abcd...efg-coreutils-8.23/bin/sleep:
+      #   cd /nix/store/abcd...efg-coreutils-8.23/bin
+      #   write /nix/store/abcd...efg-coreutils-8.23/bin/sleep sleep
+      #   sif sleep mode 040555
+      #   sif sleep gid 30000
+      # In particular, debugfs doesn't handle absolute target paths; you have to 'cd' in the virtual
+      # filesystem first. Likewise the intermediate directories must already exist (using `find`
+      # handles that for us). And when setting the file's permissions, the inode type flags (__S_IFDIR,
+      # __S_IFREG) need to be set as well.
+      (
+        echo write nix-path-registration nix-path-registration
+        echo mkdir nix
+        echo cd /nix
+        echo mkdir store
+
+        # XXX: This explodes in exciting ways if anything in /nix/store has a space in it.
+        find $storePaths -printf '%y %f %h %m\n'| while read -r type file dir perms; do
+          # echo "TYPE=$type DIR=$dir FILE=$file PERMS=$perms" >&2
+
+          echo "cd $dir"
+          case $type in
+            d)
+              echo "mkdir $file"
+              echo sif $file mode $((040000 | 0$perms)) # magic constant is __S_IFDIR
+              ;;
+            f)
+              echo "write $dir/$file $file"
+              echo sif $file mode $((0100000 | 0$perms)) # magic constant is __S_IFREG
+              ;;
+            l)
+              echo "symlink $file $(readlink "$dir/$file")"
+              ;;
+            *)
+              echo "Unknown entry: $type $dir $file $perms" >&2
+              exit 1
+              ;;
+          esac
+
+          echo sif $file gid 30000 # chgrp to nixbld
+        done
+      ) | faketime "1970-01-01 00:00:00" debugfs -w $out -f /dev/stdin > errorlog 2>&1
+
+      # The debugfs tool doesn't terminate on error nor exit with a non-zero status. Check manually.
+      if egrep -q 'Could not allocate|File not found' errorlog; then
+        cat errorlog
+        echo "--- Failed to create EXT4 image of $bytes bytes (numInodes=$numInodes, numDataBlocks=$numDataBlocks) ---"
+        return 1
+      fi
+    '';
+}
diff --git a/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix b/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
new file mode 100644
index 00000000000..0ca57a4635f
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, ... }:
+
+let
+  extlinux-conf-builder =
+    import ../../system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix {
+      inherit pkgs;
+    };
+in
+{
+  imports = [
+    ../../profiles/minimal.nix
+    ../../profiles/installation-device.nix
+    ./sd-image.nix
+  ];
+
+  assertions = lib.singleton {
+    assertion = pkgs.stdenv.system == "armv7l-linux";
+    message = "sd-image-armv7l-multiplatform.nix can be only built natively on ARMv7; " +
+      "it cannot be cross compiled";
+  };
+
+  boot.loader.grub.enable = false;
+  boot.loader.generic-extlinux-compatible.enable = true;
+
+  # FIXME: change this to linuxPackages_latest once v4.2 is out
+  boot.kernelPackages = pkgs.linuxPackages_testing;
+  boot.kernelParams = ["console=ttyS0,115200n8" "console=ttyAMA0,115200n8" "console=tty0"];
+
+  # FIXME: fix manual evaluation on ARM
+  services.nixosManual.enable = lib.mkOverride 0 false;
+
+  # FIXME: this probably should be in installation-device.nix
+  users.extraUsers.root.initialHashedPassword = "";
+
+  sdImage = {
+    populateBootCommands = ''
+        ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./boot
+    '';
+  };
+}
diff --git a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
new file mode 100644
index 00000000000..199a252ad2b
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+let
+  extlinux-conf-builder =
+    import ../../system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix {
+      inherit pkgs;
+    };
+in
+{
+  imports = [
+    ../../profiles/minimal.nix
+    ../../profiles/installation-device.nix
+    ./sd-image.nix
+  ];
+
+  assertions = lib.singleton {
+    assertion = pkgs.stdenv.system == "armv6l-linux";
+    message = "sd-image-raspberrypi.nix can be only built natively on ARMv6; " +
+      "it cannot be cross compiled";
+  };
+
+  # Needed by RPi firmware
+  nixpkgs.config.allowUnfree = true;
+
+  boot.loader.grub.enable = false;
+  boot.loader.generic-extlinux-compatible.enable = true;
+
+  boot.kernelPackages = pkgs.linuxPackages_rpi;
+
+  # FIXME: fix manual evaluation on ARM
+  services.nixosManual.enable = lib.mkOverride 0 false;
+
+  # FIXME: this probably should be in installation-device.nix
+  users.extraUsers.root.initialHashedPassword = "";
+
+  sdImage = {
+    populateBootCommands = ''
+      for f in bootcode.bin fixup.dat start.elf; do
+        cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/$f boot/
+      done
+      cp ${pkgs.ubootRaspberryPi}/u-boot.bin boot/u-boot-rpi.bin
+      echo 'kernel u-boot-rpi.bin' > boot/config.txt
+      ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./boot
+    '';
+  };
+}
diff --git a/nixos/modules/installer/cd-dvd/sd-image.nix b/nixos/modules/installer/cd-dvd/sd-image.nix
new file mode 100644
index 00000000000..12b4f304561
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/sd-image.nix
@@ -0,0 +1,127 @@
+# This module creates a bootable SD card image containing the given NixOS
+# configuration. The generated image is MBR partitioned, with a FAT /boot
+# partition, and ext4 root partition. The generated image is sized to fit
+# its contents, and a boot script automatically resizes the root partition
+# to fit the device on the first boot.
+#
+# The derivation for the SD image will be placed in
+# config.system.build.sdImage
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  rootfsImage = import ../../../lib/make-ext4-fs.nix {
+    inherit pkgs;
+    inherit (config.sdImage) storePaths;
+    volumeLabel = "NIXOS_SD";
+  };
+in
+{
+  options.sdImage = {
+    storePaths = mkOption {
+      type = with types; listOf package;
+      example = literalExample "[ pkgs.stdenv ]";
+      description = ''
+        Derivations to be included in the Nix store in the generated SD image.
+      '';
+    };
+
+    bootSize = mkOption {
+      type = types.int;
+      default = 128;
+      description = ''
+        Size of the /boot partition, in megabytes.
+      '';
+    };
+
+    populateBootCommands = mkOption {
+      example = literalExample "'' cp \${pkgs.myBootLoader}/u-boot.bin boot/ ''";
+      description = ''
+        Shell commands to populate the ./boot directory.
+        All files in that directory are copied to the
+        /boot partition on the SD image.
+      '';
+    };
+  };
+
+  config = {
+    fileSystems = {
+      "/boot" = {
+        device = "/dev/disk/by-label/NIXOS_BOOT";
+        fsType = "vfat";
+      };
+      "/" = {
+        device = "/dev/disk/by-label/NIXOS_SD";
+        fsType = "ext4";
+      };
+    };
+
+    sdImage.storePaths = [ config.system.build.toplevel ];
+
+    system.build.sdImage = pkgs.stdenv.mkDerivation {
+      name = "sd-image-${pkgs.stdenv.system}.img";
+
+      buildInputs = with pkgs; [ dosfstools e2fsprogs mtools libfaketime utillinux ];
+
+      buildCommand = ''
+        # Create the image file sized to fit /boot and /, plus 4M of slack
+        rootSizeBlocks=$(du -B 512 --apparent-size ${rootfsImage} | awk '{ print $1 }')
+        bootSizeBlocks=$((${toString config.sdImage.bootSize} * 1024 * 1024 / 512))
+        imageSize=$((rootSizeBlocks * 512 + bootSizeBlocks * 512 + 4096 * 1024))
+        truncate -s $imageSize $out
+
+        # type=b is 'W95 FAT32', type=83 is 'Linux'.
+        sfdisk $out <<EOF
+            label: dos
+            label-id: 0x2178694e
+
+            start=1M, size=$bootSizeBlocks, type=b, bootable
+            type=83
+        EOF
+
+        # Copy the rootfs into the SD image
+        eval $(partx $out -o START,SECTORS --nr 2 --pairs)
+        dd conv=notrunc if=${rootfsImage} of=$out seek=$START count=$SECTORS
+
+        # Create a FAT32 /boot partition of suitable size into bootpart.img
+        eval $(partx $out -o START,SECTORS --nr 1 --pairs)
+        truncate -s $((SECTORS * 512)) bootpart.img
+        faketime "1970-01-01 00:00:00" mkfs.vfat -i 0x2178694e -n NIXOS_BOOT bootpart.img
+
+        # Populate the files intended for /boot
+        mkdir boot
+        ${config.sdImage.populateBootCommands}
+
+        # Copy the populated /boot into the SD image
+        (cd boot; mcopy -bpsvm -i ../bootpart.img ./* ::)
+        dd conv=notrunc if=bootpart.img of=$out seek=$START count=$SECTORS
+      '';
+    };
+
+    boot.postBootCommands = ''
+      # On the first boot do some maintenance tasks
+      if [ -f /nix-path-registration ]; then
+        # Figure out device names for the boot device and root filesystem.
+        rootPart=$(readlink -f /dev/disk/by-label/NIXOS_SD)
+        bootDevice=$(lsblk -npo PKNAME $rootPart)
+
+        # Resize the root partition and the filesystem to fit the disk
+        echo ",+," | sfdisk -N2 --no-reread $bootDevice
+        ${pkgs.parted}/bin/partprobe
+        ${pkgs.e2fsprogs}/bin/resize2fs $rootPart
+
+        # Register the contents of the initial Nix store
+        ${config.nix.package}/bin/nix-store --load-db < /nix-path-registration
+
+        # nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag.
+        touch /etc/NIXOS
+        ${config.nix.package}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
+
+        # Prevents this from running on later boots.
+        rm -f /nix-path-registration
+      fi
+    '';
+  };
+}