Auto-mounting encrypted external drive in NixOS
I’ve always been bad at backing up my laptop regularly. I recently had the chance to change my laptop, and I decided to install NixOS. Since this is a fresh start, I wanted to make sure my data was regularly backed up to an external drive.
The external drive will:
- Be formatted with btrfs so that I can send incremental snapshots of my home directory
- LUKS encrypted to defend against losing the external drive.
I didn’t want to have to manually decrypt the external drive and mount the file system each time I connected it. When I looked online for information about how to do this, it was a bit scattered and didn’t focus on NixOS. In this article, we will go over how to encrypt the external hard drive, and auto-mount the encrypted external drive automatically in NixOS (although the logic is the same on other Linux distros).
If you know how to LUKS encrypt a drive, skip to the next section.
LUKS encrypt the external drive:
$ cryptsetup luksFormat /dev/nvme1n1p1
## Enter password
$ cryptsetup open /dev/nvme1n1p1 vault
$ dd bs=512 count=4 if=/dev/random of=/root/mykeyfile.key iflag=fullblock
$ cryptsetup luksAddKey /dev/nvme1n1p1 /root/mykeyfile.key
$ sudo mkfs.btrfs -L vault /dev/mapper/vault
The one thing I would recommend is to add a keyfile so that we can easily decrypt the drive instead of typing the password everytime. I am not going to explain this any further as there are numerous tutorials on LUKS encrypting a drive.
This is how the device/FS layout looks like on a real device
$ lsblk -f
nvme1n1
└─nvme1n1p1 crypto_LUKS 2 1342cc60-7514-4d70-8d1b-303b009cea34
└─vault btrfs vault e04b44ad-1beb-4902-9b91-e5e6ed43e51c 369.6G 20% /mnt/vault
Auto-mounting in NixOS:
The following needs to be executed to auto-mount an encrypted drive:
- Decrypt the drive automatically when connected. As this is an external drive, the decryption should be triggered when a particular device is connected.
- Mount the detected filesystem on the decrypted drive.
Auto-decrypt using crypttab:
systemd allows specifying the configuration for encrypted block devices through
/etc/crypttab
.noauto
option can be specified so that systemd will not try to unlock the device at boot. TheUUID
specified incrypttab
is the partition’s UUID.
environment.etc.crypttab.text = ''
vault UUID=1342cc60-7514-4d70-8d1b-303b009cea34 /root/mykeyfile.key noauto
'';
This will also generate a new systemd service unit file
systemd-cryptsetup@vault.service
. Note that this service cannot be
enabled
it is a transient/generated unit file.
To decrypt the drive manually using the systemd generated file, we could do:
$ sudo systemctl start systemd-cryptsetup@vault.service
Trigger auto-decryption with an udev rule:
We would rather not start the service every time we connect the drive. Instead, we want to start this systemd service whenever the external drive is connected. An udev rule could be specified to do exactly that.
This article covers
the basic udev syntax. We basically specify conditions to trigger a
certain outcome in the udev rule. To uniquely identify the storage
device, udevadm info <device>
could be used.
The following rule specifies that if the device belongs to SUBSYSTEM
“block” and has a specific WWN (unique identifier), then we decrypt the
drive using with ENV{SYSTEMD_WANTS}=systemd-cryptsetup@vault.service
:
services.udev.extraRules = ''
SUBSYSTEM=="block" ENV{ID_WWN}=="nvme.144d-933432304e50305234983030383659-53616d73756e6720506f727461626c6520535344205835-00000001",\
ENV{SYSTEMD_WANTS}="systemd-cryptsetup@vault.service"
'';
Mount the filesystem:
The last step is pretty trivial. The filesystem mount point needs to specified to mount the decrypted drive. Some things to keep in mind:
- UUID specified here refers to the decrypted device’s uuid(
/dev/mapper/vault
in this case). noauto
is specified again here so that systemd does not try to mount this during boot time.-
x-systemd.automount
,x-systemd.device-timeout
is specified so that systemd automounts the device if it is detected.fileSystems."/mnt/vault" = { device = "/dev/disk/by-uuid/e04b44ad-1beb-4902-9b91-e5e6ed43e51c"; fsType = "btrfs"; options = [ "defaults" "noatime" "x-systemd.automount" "x-systemd.device-timeout=5" "noauto" ]; };
Conclusion:
I believed that auto-mounting an encrypted external drive would have been significantly more straightforward than implementing bespoke udev rules. However, it was helpful to learn how each part works together.
My final external-disk.nix
:
{
environment.etc.crypttab.text = ''
vault UUID=1342cc60-7514-4d70-8d1b-303b009cea34 /root/mykeyfile.key noauto
'';
# The above crypttab creates a systemd cryptsetup vault service, which the below udev rule depends on
services.udev.extraRules = ''
SUBSYSTEM=="block" ENV{ID_WWN}=="nvme.nvme.144d-933432304e50305234983030383659-53616d73756e6720506f727461626c6520535344205835-00000001", ENV{SYSTEMD_WANTS}="systemd-cryptsetup@vault.service"
'';
fileSystems."/mnt/vault" = {
device = "/dev/disk/by-uuid/e04b44ad-1beb-4902-9b91-e5e6ed43e51c";
fsType = "btrfs";
options = [
"defaults"
"noatime"
"x-systemd.automount"
"x-systemd.device-timeout=5"
"noauto"
];
};
}
Happy Backing up your data!