209 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			209 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
# Based on `nixos/modules/installer/sd-card/sd-image.nix`
 | 
						|
{ lib, modulesPath, pkgs, config, ... }:
 | 
						|
let
 | 
						|
  inherit (lib) concatMap mkOption;
 | 
						|
 | 
						|
  cfg = config.my.disk;
 | 
						|
 | 
						|
  nixImage = (pkgs.callPackage "${modulesPath}/../lib/make-ext4-fs.nix" {
 | 
						|
    volumeLabel = "qclkos-nix";
 | 
						|
    uuid = "38bd3706-c049-430d-9e33-5cd0e437279b";
 | 
						|
    storePaths = config.system.build.toplevel;
 | 
						|
    compressImage = false;
 | 
						|
  }).overrideAttrs (o: {
 | 
						|
    buildCommand = ''
 | 
						|
      # We need to move the store up a level since we're not using this as a rootfs but as /nix
 | 
						|
      # HACK: `populateImageCommands` is executed in a subshell _before_ the store paths are copied in...
 | 
						|
      shopt -s expand_aliases
 | 
						|
      nixUpThenFaketime() {
 | 
						|
        mv rootImage/{nix/store,}
 | 
						|
        rmdir rootImage/nix
 | 
						|
 | 
						|
        faketime "$@"
 | 
						|
      }
 | 
						|
      alias faketime=nixUpThenFaketime
 | 
						|
 | 
						|
      ${o.buildCommand}
 | 
						|
    '';
 | 
						|
  });
 | 
						|
in
 | 
						|
{
 | 
						|
  options.my.disk = with lib.types; {
 | 
						|
    bootSize = mkOption {
 | 
						|
      description = "/boot size (MiB).";
 | 
						|
      type = ints.unsigned;
 | 
						|
      default = 1024;
 | 
						|
    };
 | 
						|
    persistSize = mkOption {
 | 
						|
      description = "/persist size (MiB).";
 | 
						|
      type = ints.unsigned;
 | 
						|
      default = 8192;
 | 
						|
    };
 | 
						|
 | 
						|
    populateBootCommands = mkOption {
 | 
						|
      description = ''
 | 
						|
        Shell commands to populate the ./boot directory.
 | 
						|
        All files in that directory are copied to the
 | 
						|
        /boot partition on the image.
 | 
						|
      '';
 | 
						|
    };
 | 
						|
 | 
						|
    imageBaseName = mkOption {
 | 
						|
      description = "Prefix of the name of the generated image file.";
 | 
						|
      default = "qclkos";
 | 
						|
    };
 | 
						|
    image = mkOption {
 | 
						|
      description = "Output disk image.";
 | 
						|
      type = unspecified;
 | 
						|
    };
 | 
						|
 | 
						|
    rootSize = mkOption {
 | 
						|
      description = "tmpfs root size.";
 | 
						|
      type = str;
 | 
						|
      default = "2G";
 | 
						|
    };
 | 
						|
  };
 | 
						|
 | 
						|
  config = {
 | 
						|
    fileSystems = {
 | 
						|
      "/boot" = {
 | 
						|
        device = "/dev/disk/by-label/QCLKOS_BOOT";
 | 
						|
        fsType = "vfat";
 | 
						|
      };
 | 
						|
      "/persist" = {
 | 
						|
        device = "/dev/disk/by-label/qclkos-persist";
 | 
						|
        fsType = "ext4";
 | 
						|
        neededForBoot = true;
 | 
						|
      };
 | 
						|
      "/nix" = {
 | 
						|
        device = "/dev/disk/by-label/qclkos-nix";
 | 
						|
        fsType = "ext4";
 | 
						|
      };
 | 
						|
 | 
						|
      "/" = {
 | 
						|
        device = "yeet";
 | 
						|
        fsType = "tmpfs";
 | 
						|
        options = [ "size=${cfg.rootSize}" "mode=755" ];
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
    boot = {
 | 
						|
      postBootCommands = ''
 | 
						|
        # On the first boot do some maintenance tasks
 | 
						|
        if [ -f /nix/nix-path-registration ]; then
 | 
						|
          set -euo pipefail
 | 
						|
          set -x
 | 
						|
          # Figure out device names for the boot device and nix filesystem.
 | 
						|
          nixPart=$(${pkgs.util-linux}/bin/findmnt -n -o SOURCE /nix)
 | 
						|
          bootDevice=$(lsblk -npo PKNAME $nixPart)
 | 
						|
          partNum=$(lsblk -npo MAJ:MIN $nixPart | ${pkgs.gawk}/bin/awk -F: '{print $2}')
 | 
						|
 | 
						|
          # Resize the nix partition and the filesystem to fit the disk
 | 
						|
          echo ",+," | sfdisk -N$partNum --no-reread $bootDevice
 | 
						|
          ${pkgs.parted}/bin/partprobe
 | 
						|
          ${pkgs.e2fsprogs}/bin/resize2fs $nixPart
 | 
						|
 | 
						|
          # Register the contents of the initial Nix store
 | 
						|
          ${config.nix.package.out}/bin/nix-store --load-db < /nix/nix-path-registration
 | 
						|
 | 
						|
          # nixos-rebuild also requires a "system" profile and an /etc/NIXOS tag.
 | 
						|
          touch /etc/NIXOS
 | 
						|
          ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
 | 
						|
 | 
						|
          # Prevents this from running on later boots.
 | 
						|
          rm -f /nix/nix-path-registration
 | 
						|
        fi
 | 
						|
      '';
 | 
						|
    };
 | 
						|
 | 
						|
    users.mutableUsers = false;
 | 
						|
 | 
						|
    environment = {
 | 
						|
      # This is based on parts of `nixfiles/nixos/modules/tmproot.nix`
 | 
						|
      persistence."/persist" = {
 | 
						|
        directories = [
 | 
						|
          "/var/lib/nixos"
 | 
						|
          "/var/lib/systemd"
 | 
						|
 | 
						|
          "/etc/qclk"
 | 
						|
        ];
 | 
						|
        files = [
 | 
						|
          "/etc/machine-id"
 | 
						|
        ] ++ (concatMap (k: [ k.path "${k.path}.pub" ]) config.services.openssh.hostKeys);
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
    services = {
 | 
						|
      journald.storage = "volatile";
 | 
						|
    };
 | 
						|
 | 
						|
    my.disk.image = pkgs.callPackage ({
 | 
						|
      stdenv, dosfstools, e2fsprogs, mtools, libfaketime, util-linux
 | 
						|
    }: stdenv.mkDerivation {
 | 
						|
      name = "${cfg.imageBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
 | 
						|
      nativeBuildInputs = [
 | 
						|
        dosfstools e2fsprogs libfaketime mtools util-linux
 | 
						|
      ];
 | 
						|
 | 
						|
      buildCommand = ''
 | 
						|
        mkdir -p $out
 | 
						|
        export img=$out/${cfg.imageBaseName}.img
 | 
						|
 | 
						|
        nix_fs=${nixImage}
 | 
						|
 | 
						|
        # Gap in front of the first partition, in MiB
 | 
						|
        gap=8
 | 
						|
 | 
						|
        # Create the image file sized to fit /boot and /nix, plus slack for the gap.
 | 
						|
        nixSizeBlocks=$(du -B 512 --apparent-size $nix_fs | awk '{ print $1 }')
 | 
						|
        bootSizeBlocks=$((${toString cfg.bootSize} * 1024 * 1024 / 512))
 | 
						|
        persistSizeBlocks=$((${toString cfg.persistSize} * 1024 * 1024 / 512))
 | 
						|
        imageSize=$((nixSizeBlocks * 512 + persistSizeBlocks * 512 + bootSizeBlocks * 512 + gap * 1024 * 1024))
 | 
						|
        truncate -s $imageSize $img
 | 
						|
 | 
						|
        # type=b is 'W95 FAT32', type=83 is 'Linux'.
 | 
						|
        # The "bootable" partition is where u-boot will look file for the bootloader
 | 
						|
        # information (dtbs, extlinux.conf file).
 | 
						|
        sfdisk --no-reread --no-tell-kernel $img <<EOF
 | 
						|
          label: dos
 | 
						|
          label-id: 0xabb10d5a
 | 
						|
 | 
						|
          start=''${gap}M, size=$bootSizeBlocks, type=b, bootable
 | 
						|
          start=$((gap + ${toString cfg.bootSize}))M, size=$persistSizeBlocks, type=83
 | 
						|
          start=$((gap + ${toString cfg.bootSize} + ${toString cfg.persistSize}))M, type=83
 | 
						|
        EOF
 | 
						|
 | 
						|
        # Copy the nixfs into the image
 | 
						|
        eval $(partx $img -o START,SECTORS --nr 3 --pairs)
 | 
						|
        dd conv=notrunc if=$nix_fs of=$img seek=$START count=$SECTORS
 | 
						|
 | 
						|
        # Create a FAT32 /boot partition of suitable size into boot_part.img
 | 
						|
        eval $(partx $img -o START,SECTORS --nr 1 --pairs)
 | 
						|
        truncate -s $((SECTORS * 512)) boot_part.img
 | 
						|
 | 
						|
        mkfs.vfat --invariant -n QCLKOS_BOOT boot_part.img
 | 
						|
 | 
						|
        # Populate the files intended for /boot
 | 
						|
        mkdir boot
 | 
						|
        ${cfg.populateBootCommands}
 | 
						|
 | 
						|
        find boot -exec touch --date=2000-01-01 {} +
 | 
						|
        # Copy the populated /boot into the image
 | 
						|
        cd boot
 | 
						|
        # Force a fixed order in mcopy for better determinism, and avoid file globbing
 | 
						|
        for d in $(find . -type d -mindepth 1 | sort); do
 | 
						|
          faketime "2000-01-01 00:00:00" mmd -i ../boot_part.img "::/$d"
 | 
						|
        done
 | 
						|
        for f in $(find . -type f | sort); do
 | 
						|
          mcopy -pvm -i ../boot_part.img "$f" "::/$f"
 | 
						|
        done
 | 
						|
        cd ..
 | 
						|
 | 
						|
        # Verify the FAT partition before copying it.
 | 
						|
        fsck.vfat -vn boot_part.img
 | 
						|
        dd conv=notrunc if=boot_part.img of=$img seek=$START count=$SECTORS
 | 
						|
      '';
 | 
						|
    }) { };
 | 
						|
  };
 | 
						|
}
 |