This guide provides an example setup for a ext4-single-disk ZFS system with native encryption, accessible for decryption remotely.
This guide walks you through setting up a ZFS system with native encryption and remote decryption via SSH. After completing this guide, your machine's root filesystem will be encrypted, and you will be able to unlock it remotely over the network during boot.
This guide is compatible with systems that have secure boot disabled. If you encounter boot issues, check if secure boot needs to be disabled in your UEFI settings.
Replace the highlighted lines below with your own disk ID. You can find out your disk ID by running:
ssh root@nixos-installer.local lsblk --output NAME,ID-LINK,FSTYPE,SIZE,MOUNTPOINT machines/<mymachine>/disko.nix.git add machines/<mymachine>/disko.nix so that Nix sees the file.disko.nix{
config,
lib,
pkgs,
...
}:
let
mirrorBoot = idx: {
# suffix is to prevent disk name collisions
name = idx;
type = "disk";
device = "/dev/disk/by-id/${idx}";
content = {
type = "gpt";
partitions = {
"boot" = {
size = "1M";
type = "EF02"; # for grub MBR
priority = 1;
};
"ESP" = lib.mkIf (idx == "ata-HGST_HUS726020ALE610_K5HEJXVD") {
# (1)
size = "1G";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "nofail" ];
};
};
"root" = {
size = "100%";
content = {
type = "zfs";
pool = "zroot";
};
};
};
};
};
in
{
imports = [ ];
config = {
# generates the encryption key
clan.core.vars.generators.zfs = {
files.key.neededFor = "partitioning"; # (2)
runtimeInputs = [
pkgs.xkcdpass
];
script = ''
xkcdpass -d - -n 8 | tr -d '\n' > $out/key
'';
};
# service that waits for the zfs key
boot.initrd.systemd.services.zfs-import-zroot = {
# (3)
preStart = ''
while [ ! -f ${config.clan.core.vars.generators.zfs.files.key.path} ]; do
sleep 1
done
'';
unitConfig = {
StartLimitIntervalSec = 0;
};
serviceConfig = {
RestartSec = "1s";
Restart = "on-failure";
};
};
boot.loader.grub = {
enable = true;
efiSupport = true;
efiInstallAsRemovable = true;
devices = [
"/dev/disk/by-id/ata-HGST_HUS726020ALE610_K5HEJXVD" # (5)
];
};
disko.devices = {
disk = {
x = mirrorBoot "ata-HGST_HUS726020ALE610_K5HEJXVD";
};
zpool = {
zroot = {
type = "zpool";
rootFsOptions = {
compression = "lz4";
acltype = "posixacl";
xattr = "sa";
"com.sun:auto-snapshot" = "true";
mountpoint = "none";
};
datasets = {
"root" = {
type = "zfs_fs";
options = {
mountpoint = "none";
encryption = "aes-256-gcm";
keyformat = "passphrase";
keylocation = "file://${config.clan.core.vars.generators.zfs.files.key.path}"; # (4)
};
};
"root/nixos" = {
type = "zfs_fs";
options.mountpoint = "/";
mountpoint = "/";
};
"root/home" = {
type = "zfs_fs";
options.mountpoint = "/home";
mountpoint = "/home";
};
"root/tmp" = {
type = "zfs_fs";
mountpoint = "/tmp";
options = {
mountpoint = "/tmp";
sync = "disabled";
};
};
};
};
};
};
};
}
lsblk output abovemachines/<mymachine>/disko.nix.git add machines/<mymachine>/disko.nix so that Nix sees the file.disko.nix{
config,
lib,
pkgs,
...
}:
let
mirrorBoot = idx: {
# suffix is to prevent disk name collisions
name = idx;
type = "disk";
device = "/dev/disk/by-id/${idx}";
content = {
type = "gpt";
partitions = {
"boot" = {
size = "1M";
type = "EF02"; # for grub MBR
priority = 1;
};
"ESP" = lib.mkIf (idx == "ata-HGST_HUS726020ALE610_K5HEJXVD") {
# (1)
size = "1G";
type = "EF00";
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
mountOptions = [ "nofail" ];
};
};
"root" = {
size = "100%";
content = {
type = "zfs";
pool = "zroot";
};
};
};
};
};
in
{
imports = [ ];
config = {
# generates the encryption key
clan.core.vars.generators.zfs = {
files.key.neededFor = "partitioning"; # (2)
runtimeInputs = [
pkgs.xkcdpass
];
script = ''
xkcdpass -d - -n 8 | tr -d '\n' > $out/key
'';
};
# service that waits for the zfs key
boot.initrd.systemd.services.zfs-import-zroot = {
# (3)
preStart = ''
while [ ! -f ${config.clan.core.vars.generators.zfs.files.key.path} ]; do
sleep 1
done
'';
unitConfig = {
StartLimitIntervalSec = 0;
};
serviceConfig = {
RestartSec = "1s";
Restart = "on-failure";
};
};
boot.loader.grub = {
enable = true;
efiSupport = true;
efiInstallAsRemovable = true;
devices = [
"/dev/disk/by-id/ata-HGST_HUS726020ALE610_K5HEJXVD" # (5)
"/dev/disk/by-id/ata-HGST_HUS722T2TALA600_WMC6N0L89MU9"
];
};
disko.devices = {
disk = {
x = mirrorBoot "ata-HGST_HUS726020ALE610_K5HEJXVD";
y = mirrorBoot "ata-HGST_HUS722T2TALA600_WMC6N0L89MU9";
};
zpool = {
zroot = {
type = "zpool";
rootFsOptions = {
compression = "lz4";
acltype = "posixacl";
xattr = "sa";
"com.sun:auto-snapshot" = "true";
mountpoint = "none";
};
datasets = {
"root" = {
type = "zfs_fs";
options = {
mountpoint = "none";
encryption = "aes-256-gcm";
keyformat = "passphrase";
keylocation = "file://${config.clan.core.vars.generators.zfs.files.key.path}"; # (4)
};
};
"root/nixos" = {
type = "zfs_fs";
options.mountpoint = "/";
mountpoint = "/";
};
"root/home" = {
type = "zfs_fs";
options.mountpoint = "/home";
mountpoint = "/home";
};
"root/tmp" = {
type = "zfs_fs";
mountpoint = "/tmp";
options = {
mountpoint = "/tmp";
sync = "disabled";
};
};
};
};
};
};
};
}
lsblk output aboveNext, copy the configuration below into machines/<mymachine>/initrd.nix and include it in your configuration.nix.
Don't forget to git add machines/<mymachine>/initrd.nix so that Nix sees the file.
initrd.nix{ config, pkgs, ... }:
{
boot.initrd.systemd = {
enable = true;
};
# generates host keys for the initrd ssh daemon
clan.core.vars.generators.initrd-ssh = {
files."id_ed25519".neededFor = "activation"; # (3)
files."id_ed25519.pub".secret = false;
runtimeInputs = [
pkgs.coreutils
pkgs.openssh
];
script = ''
ssh-keygen -t ed25519 -N "" -f $out/id_ed25519
'';
};
boot.initrd.network = {
enable = true;
ssh = {
enable = true;
port = 7172;
authorizedKeys = [
"<My_SSH_Public_Key>" # (1)
];
hostKeys = [
config.clan.core.vars.generators.initrd-ssh.files.id_ed25519.path
];
};
};
boot.initrd.availableKernelModules = [
"xhci_pci"
];
# Find out the required network card driver by running `nix shell nixpkgs#pciutils -c lspci -k` on the target machine
boot.initrd.kernelModules = [ "e1000e" ]; # (2)
}
<My_SSH_Public_Key> with your SSH public key.nix shell nixpkgs#pciutils -c lspci -k on the target.Before starting the installation, ensure that your SSH public key is on the NixOS installer.
Copy your public SSH key to the installer if you have not done so already:
ssh-copy-id root@nixos-installer.local -i ~/.config/clan/nixos-anywhere/keys/id_ed25519 SSH into the installer:
ssh root@nixos-installer.localWipe the existing partition table from the target disk:
blkdiscard /dev/disk/by-id/<installdisk>Run kexec and partition the disks:
clan machines install <mymachine> --target-host root@nixos-installer.local --phases kexec,diskoCheck the logs for errors before proceeding.
Install NixOS onto the partitioned disks:
clan machines install <mymachine> --target-host root@nixos-installer.local --phases installReboot the machine and remove the USB installer.
After rebooting, the machine will pause in the initrd and wait for the encryption key before continuing to boot. You can verify connectivity by SSHing into the initrd environment:
ssh root@<your-machines-ip> -p 7172 To automate the decryption step, create the following script:
machines/<mymachine>/decrypt.sh.chmod +x machines/<mymachine>/decrypt.sh.decrypt.sh#!/usr/bin/env bash
set -euxo pipefail
HOST="192.0.2.1" # (1)
MACHINE="<mymachine>" # (2)
while ! ping -W 1 -c 1 "$HOST"; do
sleep 1
done
while ! timeout --foreground 10 ssh -p 7172 "root@$HOST" true; do
sleep 1
done
# Ensure that /run/partitioning-secrets/zfs/key only ever exists with the full key
clan vars get "$MACHINE" zfs/key | ssh -p 7172 "root@${HOST}" "mkdir -p /run/partitioning-secrets/zfs && cat > /run/partitioning-secrets/zfs/key.tmp && mv /run/partitioning-secrets/zfs/key.tmp /run/partitioning-secrets/zfs/key"