Auto-unlock root on LUKS with systemd initrd

Hi everybody,

I successfully followed this guide on Wiki and NixOS automatically unlocks LUKS encrypted root during boot:

  boot.initrd.kernelModules = ["uas" "usbcore" "usb_storage" "vfat" "nls_cp437" "nls_iso8859_1" "btrfs"];

  boot.initrd.postDeviceCommands = pkgs.lib.mkBefore ''
    mkdir -m 0755 -p /key
    sleep 2
    mount -n -t btrfs -o ro `findfs UUID=b501f1b9-7714-472c-988f-3c997f146a17` /key

  boot.initrd.luks.devices."crypted" = {
    keyFile = "/key/keyfile";
    preLVM = false;

If you compare the code with the Wiki, the only difference is that for testing reasons I store the keyfile in /boot and boot is formatted with btrfs, not fat.

I wanted to try out systemd initrd so I changed the code above like this:

boot.initrd.systemd.enable = true;
# preLVM = false # boot.initrd.luks.devices.<name>.preLVM is not used by systemd stage 1.

And that doesn’t work - I’m asked for password each boot.

I did some googling and found this post so I moved the script to = {
    wantedBy = [
    before = [
    unitConfig.DefaultDependencies = "no";
    serviceConfig.Type = "oneshot";
    script = ''
      mkdir -m 0755 -p /key
      sleep 2
      mount -n -t btrfs -o ro `findfs UUID=b501f1b9-7714-472c-988f-3c997f146a17` /key

But that didn’t work either…

Could you please help me to auto unlock system root on LUKS with systemd initrd?

Thank you.

So, luks devices in systemd initrd use systemd-cryptsetup-generator, which should create a RequiresMountsFor dependency on the key file. Therefore you can probably get rid of the whole service and just make a regular ole mount unit:

boot.initrd.systemd.mounts = [{
  what = "UUID=b501f1b9-7714-472c-988f-3c997f146a17";
  where = "/key";
  type = "btrfs";

This way, the root fs will require the luks device to be unlocked, which will require the systemd-cryptsetup service to run, which will require waiting for the mounts for /key/keyfile (meaning key.mount), which will require waiting for the device with UUID=b501f1b9-7714-472c-988f-3c997f146a17 to appear. Should all just work automatically.

EDIT: To be clear, the reason this is necessary is because systemd initrd deliberately doesn’t support postDeviceCommands, or any of the options like it. These are imperative in nature rather than declarative, and basically defeat the point of systemd initrd. And while it would have been possible to add the necessary sync points in systemd units to make it work with concatenated script units, it would have been frustrating and it would have worsened the benefits of systemd initrd.

EDIT2: Oh, I should also explain why I think your service unit didn’t work. Without any key.mount unit in the picture, that RequiresMountsFor clause did nothing, so the systemd-cryptsetup service was starting before your service was mounting anything, and it was failing to find the keyfile.

This is working. Thank you very much!

So in theory if I added systemd-cryptsetup.service into before = [ ] then it’d work.

Obviously I’m not going to fix the scripted service since systemd.mounts is declarative and therefore better solution.

Thank you.

Yea but systemd-cryptsetup is a template service so you would have actually had to do systemd-cryptsetup@whatever.before = [whoever] and that’s just a mess.

Glad the mount unit works :slight_smile: