Install minimal NixOS config

Generate the initial config files

nixos-generate-config --root /mnt

This will generate the following files:

  • /etc/nixos/configuration.nix
  • /etc/nixos/hardware-configuration.nix

Create the ZFS config

    (
      set -euo pipefail

      : "${DISK1:?DISK1 is not set}"
      : "${DISK2:?DISK2 is not set}"
      : "${MNT:=/mnt}"

      CFG="${MNT}/etc/nixos/configuration.nix"
      MOD="${MNT}/etc/nixos/zfs-root.nix"

      [[ -f "$CFG" ]] || { echo >&2 "ERROR: missing $CFG (run nixos-generate-config --root /mnt)"; exit 1; }

      P1_DISK1="${DISK1}-part1"
      P1_DISK2="${DISK2}-part1"

      [[ -e "$P1_DISK1" ]] || { echo >&2 "ERROR: expected ESP path missing: $P1_DISK1"; exit 1; }
      [[ -e "$P1_DISK2" ]] || { echo >&2 "ERROR: expected ESP path missing: $P1_DISK2"; exit 1; }

      HOSTID="$(head -c4 /dev/urandom | od -A none -t x4 | tr -d ' \n')"

      echo "HOSTID:    $HOSTID"

      # --- Write zfs-root.nix ----------------------------------------------------
      cat > "$MOD" <<EOF
    { config, lib, ... }:
    let
      boot1 = "${DISK1}-part1";
      boot2 = "${DISK2}-part1";
    in
    {
      boot.supportedFilesystems = [ "zfs" ];
      boot.zfs.devNodes = "/dev/disk/by-id";

      networking.hostId = "${HOSTID}";

      # nixos-generate-config typically enables systemd-boot on UEFI; turn it off
      boot.loader.systemd-boot.enable = lib.mkForce false;

      boot.loader.grub = {
        enable = lib.mkForce true;
        efiSupport = true;
        efiInstallAsRemovable = true;
        devices = [ "nodev" ];
        copyKernels = true;

        mirroredBoots = [
          { path = "/boot";          devices = [ boot1 ]; }
          { path = "/boot-fallback"; devices = [ boot2 ]; }
        ];
      };

      # Append without self-reference (avoids infinite recursion)
      fileSystems."/boot".options = lib.mkAfter [ "nofail" ];

      services.zfs.autoScrub.enable = true;
    }
EOF
      echo "Wrote: $MOD"

      # --- Remove systemd-boot enable lines from configuration.nix ---------------
      # (so only one bootloader is enabled, and avoid redundant EFI var line)
      # --- enable SSH ---
      tmp="$(mktemp)"
      awk '
        # Drop the specific generated comment line (safe)
        /^[[:space:]]*#.*systemd-boot EFI boot loader\./ { next }

        # Drop enabling systemd-boot
        /^[[:space:]]*boot\.loader\.systemd-boot\.enable[[:space:]]*=[[:space:]]*true;[[:space:]]*$/ { next }

        # Drop redundant EFI vars line (zfs-root.nix sets it)
        /^[[:space:]]*boot\.loader\.efi\.canTouchEfiVariables[[:space:]]*=[[:space:]]*true;[[:space:]]*$/ { next }

        # Uncomment OpenSSH enable line if present
        /^[[:space:]]*#[[:space:]]*services\.openssh\.enable[[:space:]]*=[[:space:]]*true;[[:space:]]*$/ {
          sub(/^[[:space:]]*#[[:space:]]*/, "")
          print
          next
        }

        { print }
      ' "$CFG" > "$tmp"
      cp -f "$tmp" "$CFG"
      rm -f "$tmp"

      # --- Merge ./zfs-root.nix into existing imports ----------------------------
      if grep -q '[[:space:]]\./zfs-root\.nix\b' "$CFG"; then
        echo "configuration.nix already imports ./zfs-root.nix; done."
        exit 0
      fi

      # We do NOT create a new imports block. If imports= is missing, fail.
      if ! grep -q '^[[:space:]]*imports[[:space:]]*=' "$CFG"; then
        echo >&2 "ERROR: no imports= found in $CFG (unexpected)."
        exit 1
      fi

      tmp="$(mktemp)"
      awk '
        BEGIN { added=0; waiting_bracket=0 }

        # Single-line imports: imports = [ ... ];
        !added && /^[[:space:]]*imports[[:space:]]*=[[:space:]]*\[.*\][[:space:]]*;[[:space:]]*$/ {
          line=$0
          sub(/\]/, " ./zfs-root.nix ]", line)
          print line
          added=1
          next
        }

        # imports = (alone)
        /^[[:space:]]*imports[[:space:]]*=[[:space:]]*$/ {
          print
          waiting_bracket=1
          next
        }

        # imports = [ ... (possibly with trailing comment)
        !added && /^[[:space:]]*imports[[:space:]]*=[[:space:]]*\[/ {
          print
          print "    ./zfs-root.nix"
          added=1
          next
        }

        # bracket line after a standalone "imports =" (may include trailing comment)
        waiting_bracket && /^[[:space:]]*\[/ {
          print
          if (!added) {
            print "    ./zfs-root.nix"
            added=1
          }
          waiting_bracket=0
          next
        }

        { print }
      ' "$CFG" > "$tmp"

      cp -f "$tmp" "$CFG"
      rm -f "$tmp"

      if ! grep -q '[[:space:]]\./zfs-root\.nix\b' "$CFG"; then
        echo >&2 "ERROR: failed to merge ./zfs-root.nix into imports."
        echo >&2 "Imports region:"
        grep -n 'imports' -A20 "$CFG" >&2 || true
        exit 1
      fi

      echo "Merged ./zfs-root.nix into imports in $CFG"
    )

Install NixOS (minimal)

nixos-install

At the end it will ask you to set the root password.

Reboot

Unmount filesystems and reboot:

umount -R /mnt
zpool export rpool
reboot