SystemD Stage 1 Migration

This pull request makes SystemD stage 1 the default in NixOS.

I use boot.initrd.postResumeCommands for my impermanence setup. How would I migrate this to the new SystemD stage 1?

  # Rollback to Empty Root On Boot
  boot.initrd.postResumeCommands = lib.mkAfter ''
    zfs rollback -r zssd/enc/root@blank
                                                
    zfs mount zssd/enc/hdd-key
    zfs load-key zhdd/enc
    zfs unmount /hdd-key
  '';

i am using something like that to reset to a blank snapshot

      boot.initrd.systemd.services.initrd-rollback-root = {
        after = [ "zfs-import-rpool.service" ];
        requires = [ "zfs-import-rpool.service" ];
        before = [ "sysroot.mount" ];
        wantedBy = [ "initrd.target" ];
        description = "Rollback root fs";
        serviceConfig = {
          Type = "oneshot";
          ExecStart = "${config.boot.zfs.package}/sbin/zfs rollback -r rpool/local/root@blank";
        };
      };

You will obviously need to adapt to your setup. Possibly you either want to add zfs package to the system units path or use lib.getExe instead, I did just not yet bother to change it.

You obviously need to change the after/requires and i am not 100% sure that those are totally correct or if there is a more generic one in the meantime.

It works on my machine (or at least i did not notice any issue since migration to systemd initrd long time ago, i enabled it before it became default).

1 Like

Thanks for sharing your config.

I had no idea on how I should go about this, but at least now I have somewhere to start. I will take this and adapt it to my setup, and will report the result here for reference.

I currently use:

boot.initrd.systemd = {
    enable = true;
    services.rollback = {
      description = "Rollback root filesystem to a pristine state on boot";
      wantedBy = [
        # "zfs.target"
        "initrd.target"
      ];
      after = [
        "zfs-import-rpool.service"
      ];
      before = [
        "sysroot.mount"
      ];
      path = with pkgs; [
        zfs
      ];
      unitConfig.DefaultDependencies = "no";
      serviceConfig.Type = "oneshot";
      script = ''
        zfs rollback -r rpool/local/root@blank && echo "  >> >> rollback complete << <<"
      '';
    };
  };

Tbh. I don’t recall where I have it from but it seems to work :wink:

3 Likes

Hi,

I use this instead of previous boot.initrd.preDeviceCommands config to display a bootmsg

boot.initrd.systemd = {
  initrdBin = [ pkgs.coreutils ];
  services.bootmsg = {
    wantedBy = [ "sysinit.target" ];
    before = [ "systemd-cryptsetup@cryptroot.service" ];
    unitConfig.DefaultDependencies = false;
    serviceConfig = {
      Type = "oneshot";
    };
    script = ''
      ${pkgs.coreutils}/bin/echo -e "${bootmsg}"  > /dev/console
    '';
  };
};

It took severals reboot to test this.

Is there a way to test this without rebooting ?

1 Like

You can use nixos-rebuild build-vm to create an equivalent VM and test that. It’s going to be a little more tricky to make a testable encrypted root device for that, though.

1 Like

I have NixOS devices with impermanence setup that only needs rolling back the ZFS root pool. Those work fine with this:

  boot.initrd.systemd.services.zfs-rollback = {
    description = "Rollback ZFS root dataset to blank snapshot";
    wantedBy = [
      "initrd.target"
    ];
    after = [
      # This is a dynamically generated service, based on the zpool name
      "zfs-import-${ZPOOL_NAME}.service"
    ];
    before = [
      "sysroot.mount"
    ];
    path = [ pkgs.zfs ];
    unitConfig.DefaultDependencies = "no";
    serviceConfig.Type = "oneshot";
    script = ''
      zfs rollback -r ${DATASET_NAME}@blank
    '';
  };

But for my main device I have the following setup:

  • Root ZFS pool on SSD encrypted via passphrase.
  • Secondary ZFS pool on HDD encrypted by a key inside a file.
  • The file is stored on the SSD.
  • Once I enter the passphrase to unlock the SSD, the HDD key should automatically be loaded such that I don’t get an extra password prompt on each boot.

With scripted stage 1, I achieved what I want with this config:

  # Rollback to Empty Root On Boot
  boot.initrd.postResumeCommands = lib.mkAfter ''
    zfs rollback -r zssd/enc/root@blank
                                                
    zfs mount zssd/enc/hdd-key
    zfs load-key zhdd/enc
    zfs unmount /hdd-key
  '';

This is not as straightforward to migrate to SystemD stage 1. When I do the migration and reboot, loading the key fails, but it prevents booting so I can’t inspect or debug anything.

I figured out how to make my two-disk setup work with SystemD Stage 1.

I’m reporting the final solution here in hope to help someone facing the same issue.

  boot.initrd.systemd.services.zssd-rollback = {
    description = "Rollback ZFS root dataset to blank snapshot";
    wantedBy = [
      "initrd.target"
    ];
    after = [
      # Dynamically generated service based on the zpool name
      "zfs-import-zssd.service"
    ];
    before = [
      "sysroot.mount"
    ];
    path = [ pkgs.zfs ];
    unitConfig.DefaultDependencies = "no";
    serviceConfig.Type = "oneshot";
    script = ''
      zfs rollback -r zssd/enc/root@blank
    '';
  };

  boot.initrd.systemd.services.zhdd-load-key = {
    description = "Load key for ZHDD pool from ZSSD pool";
    wantedBy = [
      "initrd.target"
    ];
    after = [
      # Dynamically generated services based on the zpool names
      "zfs-import-zssd.service"
      "zfs-import-zhdd.service"
    ];
    before = [
      "sysroot.mount"
    ];
    path = [ pkgs.zfs ];
    unitConfig.DefaultDependencies = "no";
    serviceConfig.Type = "oneshot";
    script = ''
      zfs mount zssd/enc/hdd-key
      zfs load-key zhdd/enc
      zfs unmount /hdd-key
    '';
  };

Probably not the cleanest one, but it works. If you have a cleaner solution, feel free to share it here.

1 Like