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… ![]()
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:
-
the eMMC block device isn’t appearing early enough (missing module, timing), or
-
the eMMC appears, but the by-partlabel symlink never gets created / isn’t matching what disko actually wrote, or
-
kernel/version difference (GUI installer kernel vs my pinned 6.6) changes eMMC/SDHCI behavior.
What I need help with or would greatly appreciate
- Debugging path in initrd:
What’s the best way to narrow this down? (e.g. dropping into initrd emergency shell and checkingdmesg | 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? - 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? - Missing initrd modules for IdeaPad 120S eMMC:
Are there specific modules commonly required here that I’m missing? (I includedmmc_core,mmc_block,sdhci,sdhci_acpi,rtsx_pci_sdmmc.)
Should I also include things likesdhci_pci(or any other common SDHCI/MMC host controller modules) even if I think ACPI is the one? - 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.