# 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 <