Lenovo Ideapad 120S Luks->LVM->Ext4 not booting up

NixOS installation not booting MMC with Luks→LVM→Ext4 (Lenovo Ideapad 120S)

The problem

I’m trying to deploy NixOS onto a Lenovo IdeaPad 120S with 64GB eMMC (/dev/mmcblk0) using nixos-anywhere + disko. Yes, the device is old/not highly specced… :rofl:

Storage layout is GPT → ESP → LUKS2 → LVM VG (thinpool) → ext4 with an “impermanence-style” ephemeral root (thin snapshot created at install time).

The deploy itself completes fine (format/mount/install all succeed), but the machine won’t boot afterwards.


On reboot, systemd-boot loads the kernel+initrd fine, but stage-1 gets stuck and then fails before the LUKS unlock prompt even appears.

It consistently hangs on:

  • waiting 10 seconds for device /dev/disk/by-partlabel/host_crypt to appear ...

  • then fails right after udev is loaded (so it looks like the eMMC block device / the by-partlabel symlink never shows up in initrd in time, or at all)

Important detail: If I install via the GUI installer with a simple LUKS → ext4 layout on the same eMMC, the system boots fine. So the hardware + basic encryption path works; the failure seems tied to my disko/nixos-anywhere layout and/or initrd device discovery timing.

disk-config.nix

{ ... }:
let
  disk = "/dev/mmcblk0";
in
{
  disko.devices = {
    disk.main = {
      type = "disk";
      device = disk;
      content = {
        type = "gpt";
        partitions = {
          ESP = {
            priority = 1;
            size = "512M";
            type = "EF00";
            content = {
              type = "filesystem";
              format = "vfat";
              mountpoint = "/boot";
              mountOptions = [ "fmask=0077" "dmask=0077" ];
            };
          };

          luks = {
            size = "100%";
            label = "host_crypt"; # expected: /dev/disk/by-partlabel/host_crypt
            content = {
              type = "luks";
              name = "cryptroot";
              settings.allowDiscards = true;

              extraFormatArgs = [
                "--type" "luks2"
                "--cipher" "aes-xts-plain64"
                "--key-size" "512"
                "--hash" "sha256"
                "--pbkdf" "argon2id"
                "--iter-time" "2000"
              ];

              # only for provisioning during install:
              passwordFile = "/tmp/luks-pass";

              content = {
                type = "lvm_pv";
                vg = "vg";
              };
            };
          };
        };
      };
    };

    lvm_vg.vg = {
      type = "lvm_vg";
      lvs = {
        thinpool = {
          size = "52G";
          lvm_type = "thin-pool";
          extraArgs = [
            "--poolmetadatasize" "200M"
            "--chunksize" "64K"
          ];
        };

        swap = {
          size = "4G";
          content.type = "swap";
        };

        root = {
          size = "12G";
          lvm_type = "thinlv";
          pool = "thinpool";
          content = {
            type = "filesystem";
            format = "ext4";
            mountpoint = "/";
            mountOptions = [ "noatime" "discard" ];
            postCreateHook = ''
              sleep 2
              lvcreate --snapshot --name root-blank vg/root
              echo "✓ Created pristine snapshot: vg/root-blank"
            '';
          };
        };

        persist = {
          size = "10G";
          lvm_type = "thinlv";
          pool = "thinpool";
          content = {
            type = "filesystem";
            format = "ext4";
            mountpoint = "/persist";
            mountOptions = [ "noatime" "discard" ];
          };
        };

        nix = {
          size = "28G";
          lvm_type = "thinlv";
          pool = "thinpool";
          content = {
            type = "filesystem";
            format = "ext4";
            mountpoint = "/nix";
            mountOptions = [ "noatime" "discard" ];
          };
        };
      };
    };
  };
}

hardware-configuration.nix

{ inputs, pkgs, ... }:
{
  imports = [
    inputs.hardware.nixosModules.common-cpu-intel
    inputs.hardware.nixosModules.common-gpu-intel
  ];

  boot = {
    initrd = {
      availableKernelModules = [
        "xhci_pci"
        "ahci"
        "usb_storage"
        "sd_mod"
        "usbhid"
        "hid_generic"

        "mmc_block"
        "mmc_core"
        "sdhci"
        "sdhci_acpi"
        "rtsx_pci_sdmmc"
      ];

      kernelModules = [
        "mmc_block"
        "mmc_core"
        "sdhci"
        "sdhci_acpi"
        "rtsx_pci_sdmmc"
      ];
    };

    kernelModules = [ "i915" "kvm-intel" ];

    loader = {
      systemd-boot.enable = true;
      systemd-boot.consoleMode = "keep";
      systemd-boot.configurationLimit = 3;
      efi.canTouchEfiVariables = true;
      timeout = 0;
    };

    kernelParams = [
      "rootwait" # hoping to give eMMC time to appear
    ];

    kernelPackages = pkgs.linuxPackages_6_6;
  };

  networking.hostName = "host";
  services.fwupd.enable = true;
}

What I think is happening (more or less)

Stage-1 expects /dev/disk/by-partlabel/host_crypt, but in initrd either:

  1. the eMMC block device isn’t appearing early enough (missing module, timing), or

  2. the eMMC appears, but the by-partlabel symlink never gets created / isn’t matching what disko actually wrote, or

  3. kernel/version difference (GUI installer kernel vs my pinned 6.6) changes eMMC/SDHCI behavior.

What I need help with or would greatly appreciate

  1. Debugging path in initrd:
    What’s the best way to narrow this down? (e.g. dropping into initrd emergency shell and checking dmesg | grep -i mmc, ls -l /dev/disk/by-partlabel, lsmod, udevadm settle, etc.)
    Any recommended NixOS options/kernel params to make stage-1 more verbose / keep an emergency shell available?
  2. Partlabel correctness:
    Is relying on /dev/disk/by-partlabel/... known to be flaky in initrd for some eMMC setups?
    Would you recommend switching the LUKS device reference to /dev/disk/by-uuid/... instead (at least for the LUKS container), or does that just paper over the real issue?
  3. Missing initrd modules for IdeaPad 120S eMMC:
    Are there specific modules commonly required here that I’m missing? (I included mmc_core, mmc_block, sdhci, sdhci_acpi, rtsx_pci_sdmmc.)
    Should I also include things like sdhci_pci (or any other common SDHCI/MMC host controller modules) even if I think ACPI is the one?
  4. Similar configs to compare:
    If anyone has a working LUKS → LVM thin → ext4 setup (bonus points for impermanence-style ephemeral root) on GitHub, I’d love to compare: subtle ordering/timing differences might be the whole bug here.

If some information is missing, please let me know and I will try to provide it.

Thanks for any pointers — I’m clearly missing one boring little detail, and those are always the most powerful kinds of details.

I have finally figured it out and the problem was simple…:

The module that was missing was: sdhci_pci

I must have overlooked this several times. :joy: Comparing my config with facters results was the solution in the end.